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.
Files changed (357) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/apps/viewer/editor.html +43 -0
  5. package/apps/viewer/explore.html +27 -0
  6. package/apps/viewer/index.html +26 -0
  7. package/apps/viewer/package.json +27 -0
  8. package/apps/viewer/play.html +26 -0
  9. package/apps/viewer/plugin.d.ts +18 -0
  10. package/apps/viewer/plugin.js +75 -0
  11. package/apps/viewer/plugin.ts +84 -0
  12. package/apps/viewer/src/adlib.d.ts +20 -0
  13. package/apps/viewer/src/adlib.js +55 -0
  14. package/apps/viewer/src/adlib.ts +62 -0
  15. package/apps/viewer/src/app.d.ts +5 -0
  16. package/apps/viewer/src/app.js +5 -0
  17. package/apps/viewer/src/app.ts +5 -0
  18. package/apps/viewer/src/asset-index.d.ts +22 -0
  19. package/apps/viewer/src/asset-index.js +74 -0
  20. package/apps/viewer/src/asset-index.ts +90 -0
  21. package/apps/viewer/src/audio.d.ts +28 -0
  22. package/apps/viewer/src/audio.js +102 -0
  23. package/apps/viewer/src/audio.ts +110 -0
  24. package/apps/viewer/src/editor-handles.d.ts +63 -0
  25. package/apps/viewer/src/editor-handles.js +332 -0
  26. package/apps/viewer/src/editor-handles.ts +368 -0
  27. package/apps/viewer/src/editor-languages.d.ts +23 -0
  28. package/apps/viewer/src/editor-languages.js +157 -0
  29. package/apps/viewer/src/editor-languages.ts +135 -0
  30. package/apps/viewer/src/editor.css +213 -0
  31. package/apps/viewer/src/editor.d.ts +1 -0
  32. package/apps/viewer/src/editor.js +393 -0
  33. package/apps/viewer/src/editor.ts +412 -0
  34. package/apps/viewer/src/explore/classes.d.ts +7 -0
  35. package/apps/viewer/src/explore/classes.js +63 -0
  36. package/apps/viewer/src/explore/classes.ts +61 -0
  37. package/apps/viewer/src/explore/context.d.ts +33 -0
  38. package/apps/viewer/src/explore/context.js +96 -0
  39. package/apps/viewer/src/explore/context.ts +124 -0
  40. package/apps/viewer/src/explore/explore.css +93 -0
  41. package/apps/viewer/src/explore/explore.d.ts +1 -0
  42. package/apps/viewer/src/explore/explore.js +102 -0
  43. package/apps/viewer/src/explore/explore.ts +104 -0
  44. package/apps/viewer/src/explore/inspect.d.ts +5 -0
  45. package/apps/viewer/src/explore/inspect.js +208 -0
  46. package/apps/viewer/src/explore/inspect.ts +279 -0
  47. package/apps/viewer/src/explore/live.d.ts +22 -0
  48. package/apps/viewer/src/explore/live.js +318 -0
  49. package/apps/viewer/src/explore/live.ts +365 -0
  50. package/apps/viewer/src/explore/map.d.ts +11 -0
  51. package/apps/viewer/src/explore/map.js +182 -0
  52. package/apps/viewer/src/explore/map.ts +193 -0
  53. package/apps/viewer/src/files.d.ts +21 -0
  54. package/apps/viewer/src/files.js +69 -0
  55. package/apps/viewer/src/files.ts +72 -0
  56. package/apps/viewer/src/game-profile.d.ts +19 -0
  57. package/apps/viewer/src/game-profile.js +4 -0
  58. package/apps/viewer/src/game-profile.ts +26 -0
  59. package/apps/viewer/src/main.d.ts +1 -0
  60. package/apps/viewer/src/main.js +473 -0
  61. package/apps/viewer/src/main.ts +527 -0
  62. package/apps/viewer/src/music.d.ts +14 -0
  63. package/apps/viewer/src/music.js +32 -0
  64. package/apps/viewer/src/music.ts +34 -0
  65. package/apps/viewer/src/opl-worklet.d.ts +1 -0
  66. package/apps/viewer/src/opl-worklet.js +47 -0
  67. package/apps/viewer/src/opl-worklet.ts +55 -0
  68. package/apps/viewer/src/overlay.d.ts +31 -0
  69. package/apps/viewer/src/overlay.js +103 -0
  70. package/apps/viewer/src/overlay.ts +123 -0
  71. package/apps/viewer/src/play.css +14 -0
  72. package/apps/viewer/src/play.d.ts +1 -0
  73. package/apps/viewer/src/play.js +179 -0
  74. package/apps/viewer/src/play.ts +177 -0
  75. package/apps/viewer/src/saves.d.ts +10 -0
  76. package/apps/viewer/src/saves.js +50 -0
  77. package/apps/viewer/src/saves.ts +51 -0
  78. package/apps/viewer/src/session.d.ts +91 -0
  79. package/apps/viewer/src/session.js +301 -0
  80. package/apps/viewer/src/session.ts +321 -0
  81. package/apps/viewer/src/sound-preview.d.ts +7 -0
  82. package/apps/viewer/src/sound-preview.js +214 -0
  83. package/apps/viewer/src/sound-preview.ts +228 -0
  84. package/apps/viewer/src/style.css +64 -0
  85. package/apps/viewer/vite.config.ts +25 -0
  86. package/bin/sci-ts.mjs +9 -0
  87. package/lib/994.sc +49 -0
  88. package/lib/995.sc +53 -0
  89. package/lib/996.sc +299 -0
  90. package/lib/997.sc +198 -0
  91. package/lib/998.sc +338 -0
  92. package/lib/999.sc +189 -0
  93. package/lib/resources.d.ts +2 -0
  94. package/lib/resources.js +84 -0
  95. package/lib/resources.ts +88 -0
  96. package/lib/system.sh +62 -0
  97. package/package.json +75 -4
  98. package/packages/content/room.schema.json +267 -0
  99. package/packages/content/src/index.d.ts +3 -0
  100. package/packages/content/src/index.js +3 -0
  101. package/packages/content/src/index.ts +3 -0
  102. package/packages/content/src/room.d.ts +111 -0
  103. package/packages/content/src/room.js +886 -0
  104. package/packages/content/src/room.ts +1011 -0
  105. package/packages/content/src/target.d.ts +80 -0
  106. package/packages/content/src/target.js +1 -0
  107. package/packages/content/src/target.ts +60 -0
  108. package/packages/content/src/yarn.d.ts +90 -0
  109. package/packages/content/src/yarn.js +214 -0
  110. package/packages/content/src/yarn.ts +261 -0
  111. package/packages/sci/src/analysis/game.d.ts +90 -0
  112. package/packages/sci/src/analysis/game.js +221 -0
  113. package/packages/sci/src/analysis/game.ts +305 -0
  114. package/packages/sci/src/analysis/runtime.d.ts +58 -0
  115. package/packages/sci/src/analysis/runtime.js +133 -0
  116. package/packages/sci/src/analysis/runtime.ts +163 -0
  117. package/packages/sci/src/audio/adlib.d.ts +73 -0
  118. package/packages/sci/src/audio/adlib.js +280 -0
  119. package/packages/sci/src/audio/adlib.ts +329 -0
  120. package/packages/sci/src/audio/index.d.ts +43 -0
  121. package/packages/sci/src/audio/index.js +99 -0
  122. package/packages/sci/src/audio/index.ts +122 -0
  123. package/packages/sci/src/audio/midi.d.ts +92 -0
  124. package/packages/sci/src/audio/midi.js +242 -0
  125. package/packages/sci/src/audio/midi.ts +262 -0
  126. package/packages/sci/src/audio/opl2.d.ts +74 -0
  127. package/packages/sci/src/audio/opl2.js +283 -0
  128. package/packages/sci/src/audio/opl2.ts +283 -0
  129. package/packages/sci/src/audio/sol.d.ts +32 -0
  130. package/packages/sci/src/audio/sol.js +81 -0
  131. package/packages/sci/src/audio/sol.ts +100 -0
  132. package/packages/sci/src/audio/write.d.ts +35 -0
  133. package/packages/sci/src/audio/write.js +91 -0
  134. package/packages/sci/src/audio/write.ts +108 -0
  135. package/packages/sci/src/gfx/cel.d.ts +36 -0
  136. package/packages/sci/src/gfx/cel.js +82 -0
  137. package/packages/sci/src/gfx/cel.ts +110 -0
  138. package/packages/sci/src/gfx/palette.d.ts +24 -0
  139. package/packages/sci/src/gfx/palette.js +49 -0
  140. package/packages/sci/src/gfx/palette.ts +56 -0
  141. package/packages/sci/src/gfx/pic-file.d.ts +46 -0
  142. package/packages/sci/src/gfx/pic-file.js +114 -0
  143. package/packages/sci/src/gfx/pic-file.ts +155 -0
  144. package/packages/sci/src/gfx/pic.d.ts +27 -0
  145. package/packages/sci/src/gfx/pic.js +34 -0
  146. package/packages/sci/src/gfx/pic.ts +53 -0
  147. package/packages/sci/src/gfx/quantize.d.ts +42 -0
  148. package/packages/sci/src/gfx/quantize.js +199 -0
  149. package/packages/sci/src/gfx/quantize.ts +218 -0
  150. package/packages/sci/src/gfx/view-file.d.ts +62 -0
  151. package/packages/sci/src/gfx/view-file.js +182 -0
  152. package/packages/sci/src/gfx/view-file.ts +233 -0
  153. package/packages/sci/src/gfx/view.d.ts +28 -0
  154. package/packages/sci/src/gfx/view.js +45 -0
  155. package/packages/sci/src/gfx/view.ts +62 -0
  156. package/packages/sci/src/index.d.ts +38 -0
  157. package/packages/sci/src/index.js +38 -0
  158. package/packages/sci/src/index.ts +38 -0
  159. package/packages/sci/src/motion/pathfinding.d.ts +40 -0
  160. package/packages/sci/src/motion/pathfinding.js +140 -0
  161. package/packages/sci/src/motion/pathfinding.ts +147 -0
  162. package/packages/sci/src/resource/archive.d.ts +16 -0
  163. package/packages/sci/src/resource/archive.js +51 -0
  164. package/packages/sci/src/resource/archive.ts +56 -0
  165. package/packages/sci/src/resource/lzs.d.ts +12 -0
  166. package/packages/sci/src/resource/lzs.js +64 -0
  167. package/packages/sci/src/resource/lzs.ts +66 -0
  168. package/packages/sci/src/resource/manager.d.ts +45 -0
  169. package/packages/sci/src/resource/manager.js +158 -0
  170. package/packages/sci/src/resource/manager.ts +184 -0
  171. package/packages/sci/src/resource/map.d.ts +18 -0
  172. package/packages/sci/src/resource/map.js +34 -0
  173. package/packages/sci/src/resource/map.ts +44 -0
  174. package/packages/sci/src/resource/types.d.ts +37 -0
  175. package/packages/sci/src/resource/types.js +45 -0
  176. package/packages/sci/src/resource/types.ts +57 -0
  177. package/packages/sci/src/script/assembly.d.ts +67 -0
  178. package/packages/sci/src/script/assembly.js +748 -0
  179. package/packages/sci/src/script/assembly.ts +714 -0
  180. package/packages/sci/src/script/compiler.d.ts +75 -0
  181. package/packages/sci/src/script/compiler.js +993 -0
  182. package/packages/sci/src/script/compiler.ts +1040 -0
  183. package/packages/sci/src/script/disasm.d.ts +81 -0
  184. package/packages/sci/src/script/disasm.js +318 -0
  185. package/packages/sci/src/script/disasm.ts +323 -0
  186. package/packages/sci/src/script/kernel-names.d.ts +6 -0
  187. package/packages/sci/src/script/kernel-names.js +165 -0
  188. package/packages/sci/src/script/kernel-names.ts +165 -0
  189. package/packages/sci/src/script/opcodes.d.ts +22 -0
  190. package/packages/sci/src/script/opcodes.js +45 -0
  191. package/packages/sci/src/script/opcodes.ts +72 -0
  192. package/packages/sci/src/script/script.d.ts +56 -0
  193. package/packages/sci/src/script/script.js +63 -0
  194. package/packages/sci/src/script/script.ts +111 -0
  195. package/packages/sci/src/script/sexpr.d.ts +45 -0
  196. package/packages/sci/src/script/sexpr.js +151 -0
  197. package/packages/sci/src/script/sexpr.ts +139 -0
  198. package/packages/sci/src/script/vocab.d.ts +15 -0
  199. package/packages/sci/src/script/vocab.js +44 -0
  200. package/packages/sci/src/script/vocab.ts +48 -0
  201. package/packages/sci/src/script/world.d.ts +25 -0
  202. package/packages/sci/src/script/world.js +65 -0
  203. package/packages/sci/src/script/world.ts +70 -0
  204. package/packages/sci/src/text/font.d.ts +37 -0
  205. package/packages/sci/src/text/font.js +103 -0
  206. package/packages/sci/src/text/font.ts +125 -0
  207. package/packages/sci/src/text/message.d.ts +69 -0
  208. package/packages/sci/src/text/message.js +171 -0
  209. package/packages/sci/src/text/message.ts +205 -0
  210. package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
  211. package/packages/sci/src/vm/kernels/arrays.js +182 -0
  212. package/packages/sci/src/vm/kernels/arrays.ts +164 -0
  213. package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
  214. package/packages/sci/src/vm/kernels/audio.js +347 -0
  215. package/packages/sci/src/vm/kernels/audio.ts +349 -0
  216. package/packages/sci/src/vm/kernels/core.d.ts +5 -0
  217. package/packages/sci/src/vm/kernels/core.js +87 -0
  218. package/packages/sci/src/vm/kernels/core.ts +91 -0
  219. package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
  220. package/packages/sci/src/vm/kernels/graphics.js +467 -0
  221. package/packages/sci/src/vm/kernels/graphics.ts +502 -0
  222. package/packages/sci/src/vm/kernels/index.d.ts +8 -0
  223. package/packages/sci/src/vm/kernels/index.js +29 -0
  224. package/packages/sci/src/vm/kernels/index.ts +32 -0
  225. package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
  226. package/packages/sci/src/vm/kernels/lists.js +127 -0
  227. package/packages/sci/src/vm/kernels/lists.ts +111 -0
  228. package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
  229. package/packages/sci/src/vm/kernels/motion.js +204 -0
  230. package/packages/sci/src/vm/kernels/motion.ts +194 -0
  231. package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
  232. package/packages/sci/src/vm/kernels/palette.js +292 -0
  233. package/packages/sci/src/vm/kernels/palette.ts +303 -0
  234. package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
  235. package/packages/sci/src/vm/kernels/saves.js +123 -0
  236. package/packages/sci/src/vm/kernels/saves.ts +149 -0
  237. package/packages/sci/src/vm/kernels/system.d.ts +62 -0
  238. package/packages/sci/src/vm/kernels/system.js +110 -0
  239. package/packages/sci/src/vm/kernels/system.ts +128 -0
  240. package/packages/sci/src/vm/kernels/text.d.ts +13 -0
  241. package/packages/sci/src/vm/kernels/text.js +338 -0
  242. package/packages/sci/src/vm/kernels/text.ts +328 -0
  243. package/packages/sci/src/vm/memory.d.ts +138 -0
  244. package/packages/sci/src/vm/memory.js +168 -0
  245. package/packages/sci/src/vm/memory.ts +266 -0
  246. package/packages/sci/src/vm/replace.d.ts +11 -0
  247. package/packages/sci/src/vm/replace.js +14 -0
  248. package/packages/sci/src/vm/replace.ts +16 -0
  249. package/packages/sci/src/vm/savegame.d.ts +89 -0
  250. package/packages/sci/src/vm/savegame.js +161 -0
  251. package/packages/sci/src/vm/savegame.ts +188 -0
  252. package/packages/sci/src/vm/value.d.ts +17 -0
  253. package/packages/sci/src/vm/value.js +12 -0
  254. package/packages/sci/src/vm/value.ts +25 -0
  255. package/packages/sci/src/vm/vm.d.ts +155 -0
  256. package/packages/sci/src/vm/vm.js +1053 -0
  257. package/packages/sci/src/vm/vm.ts +914 -0
  258. package/tools/adlib-render.d.ts +1 -0
  259. package/tools/adlib-render.js +49 -0
  260. package/tools/adlib-render.ts +42 -0
  261. package/tools/analyze.d.ts +1 -0
  262. package/tools/analyze.js +25 -0
  263. package/tools/analyze.ts +25 -0
  264. package/tools/art/build.d.ts +34 -0
  265. package/tools/art/build.js +175 -0
  266. package/tools/art/build.ts +153 -0
  267. package/tools/art.d.ts +1 -0
  268. package/tools/art.js +31 -0
  269. package/tools/art.ts +29 -0
  270. package/tools/asm.d.ts +1 -0
  271. package/tools/asm.js +61 -0
  272. package/tools/asm.ts +55 -0
  273. package/tools/cli.d.ts +1 -0
  274. package/tools/cli.js +99 -0
  275. package/tools/cli.ts +100 -0
  276. package/tools/editor-server.d.ts +29 -0
  277. package/tools/editor-server.js +162 -0
  278. package/tools/editor-server.ts +176 -0
  279. package/tools/explore.d.ts +1 -0
  280. package/tools/explore.js +305 -0
  281. package/tools/explore.ts +250 -0
  282. package/tools/game/audio.d.ts +19 -0
  283. package/tools/game/audio.js +143 -0
  284. package/tools/game/audio.ts +130 -0
  285. package/tools/game/build.d.ts +46 -0
  286. package/tools/game/build.js +259 -0
  287. package/tools/game/build.ts +283 -0
  288. package/tools/game/defaults.d.ts +26 -0
  289. package/tools/game/defaults.js +82 -0
  290. package/tools/game/defaults.ts +83 -0
  291. package/tools/game/font.d.ts +3 -0
  292. package/tools/game/font.js +124 -0
  293. package/tools/game/font.ts +126 -0
  294. package/tools/game/kit.d.ts +7 -0
  295. package/tools/game/kit.js +7 -0
  296. package/tools/game/kit.ts +10 -0
  297. package/tools/game/new.d.ts +8 -0
  298. package/tools/game/new.js +39 -0
  299. package/tools/game/new.ts +38 -0
  300. package/tools/game/target.d.ts +7 -0
  301. package/tools/game/target.js +41 -0
  302. package/tools/game/target.ts +42 -0
  303. package/tools/game/template/resources.ts.tmpl +43 -0
  304. package/tools/game/template/rooms/1.room.yaml +9 -0
  305. package/tools/game/template/rooms/1.yarn +17 -0
  306. package/tools/game/template/scripts/0.sc +24 -0
  307. package/tools/game.d.ts +1 -0
  308. package/tools/game.js +77 -0
  309. package/tools/game.ts +70 -0
  310. package/tools/mod-build.d.ts +28 -0
  311. package/tools/mod-build.js +173 -0
  312. package/tools/mod-build.ts +179 -0
  313. package/tools/mod-target.d.ts +16 -0
  314. package/tools/mod-target.js +26 -0
  315. package/tools/mod-target.ts +32 -0
  316. package/tools/mod.d.ts +1 -0
  317. package/tools/mod.js +47 -0
  318. package/tools/mod.ts +45 -0
  319. package/tools/msg.d.ts +1 -0
  320. package/tools/msg.js +40 -0
  321. package/tools/msg.ts +36 -0
  322. package/tools/node-files.d.ts +3 -0
  323. package/tools/node-files.js +34 -0
  324. package/tools/node-files.ts +33 -0
  325. package/tools/patches.d.ts +3 -0
  326. package/tools/patches.js +13 -0
  327. package/tools/patches.ts +13 -0
  328. package/tools/pic-build.d.ts +9 -0
  329. package/tools/pic-build.js +26 -0
  330. package/tools/pic-build.ts +27 -0
  331. package/tools/pic.d.ts +1 -0
  332. package/tools/pic.js +64 -0
  333. package/tools/pic.ts +61 -0
  334. package/tools/play.d.ts +1 -0
  335. package/tools/play.js +54 -0
  336. package/tools/play.ts +54 -0
  337. package/tools/png.d.ts +16 -0
  338. package/tools/png.js +124 -0
  339. package/tools/png.ts +123 -0
  340. package/tools/res.d.ts +1 -0
  341. package/tools/res.js +48 -0
  342. package/tools/res.ts +50 -0
  343. package/tools/scenario.d.ts +1 -0
  344. package/tools/scenario.js +74 -0
  345. package/tools/scenario.ts +80 -0
  346. package/tools/site/build.d.ts +8 -0
  347. package/tools/site/build.js +110 -0
  348. package/tools/site/build.ts +116 -0
  349. package/tools/site/markdown.d.ts +11 -0
  350. package/tools/site/markdown.js +88 -0
  351. package/tools/site/markdown.ts +84 -0
  352. package/tools/view-build.d.ts +34 -0
  353. package/tools/view-build.js +47 -0
  354. package/tools/view-build.ts +71 -0
  355. package/tools/view.d.ts +1 -0
  356. package/tools/view.js +54 -0
  357. package/tools/view.ts +52 -0
@@ -0,0 +1,143 @@
1
+ import { SoundDevice } from "../../packages/sci/src/index.js";
2
+ /**
3
+ * Music and sound for games, from the files people make them with: Standard MIDI Files
4
+ * (.mid, from any sequencer) become sound resources; WAV files (PCM) become digital effects.
5
+ */
6
+ export class AudioFileError extends Error {
7
+ }
8
+ const CONTROL_CHANNEL = 15;
9
+ /**
10
+ * A Standard MIDI File (format 0 or 1) as a sound resource for General MIDI: its tempo
11
+ * changes turned into SCI's 1/60 s ticks, a channel stream per MIDI channel it uses. SCI
12
+ * keeps channel 16 (15 counting from 0) for signals to the scripts, so music there moves to
13
+ * a free channel.
14
+ */
15
+ export function readMidiFile(data) {
16
+ const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
17
+ const tag = (at) => String.fromCharCode(...data.subarray(at, at + 4));
18
+ if (tag(0) !== "MThd")
19
+ throw new AudioFileError("not a MIDI file (no MThd)");
20
+ const format = v.getUint16(8), trackCount = v.getUint16(10), division = v.getUint16(12);
21
+ if (format > 1)
22
+ throw new AudioFileError(`MIDI format ${format} isn't supported (save as format 0 or 1)`);
23
+ if (division & 0x8000)
24
+ throw new AudioFileError("SMPTE-timed MIDI files aren't supported (use ticks per quarter note)");
25
+ const events = [];
26
+ const tempos = [];
27
+ let pos = 8 + v.getUint32(4), order = 0;
28
+ for (let t = 0; t < trackCount; t++) {
29
+ if (tag(pos) !== "MTrk")
30
+ throw new AudioFileError(`track ${t + 1}: expected MTrk`);
31
+ const end = pos + 8 + v.getUint32(pos + 4);
32
+ pos += 8;
33
+ let tick = 0, running = 0;
34
+ const varLen = () => {
35
+ let n = 0, b;
36
+ do {
37
+ b = data[pos++];
38
+ n = (n << 7) | (b & 0x7f);
39
+ } while (b & 0x80 && pos < end);
40
+ return n;
41
+ };
42
+ while (pos < end) {
43
+ tick += varLen();
44
+ let status = data[pos];
45
+ if (status & 0x80)
46
+ pos++;
47
+ else if (running)
48
+ status = running;
49
+ else
50
+ throw new AudioFileError(`track ${t + 1}: data before any status byte`);
51
+ if (status === 0xff) {
52
+ const type = data[pos++], len = varLen();
53
+ if (type === 0x51 && len === 3)
54
+ tempos.push({ tick, usPerQuarter: (data[pos] << 16) | (data[pos + 1] << 8) | data[pos + 2] });
55
+ pos += len;
56
+ if (type === 0x2f)
57
+ break;
58
+ continue;
59
+ }
60
+ if (status === 0xf0 || status === 0xf7) {
61
+ pos += varLen();
62
+ continue;
63
+ }
64
+ running = status;
65
+ const n = (status & 0xf0) === 0xc0 || (status & 0xf0) === 0xd0 ? 1 : 2;
66
+ events.push({ tick, bytes: [status, ...data.subarray(pos, pos + n)], order: order++ });
67
+ pos += n;
68
+ }
69
+ pos = end;
70
+ }
71
+ // MIDI ticks to seconds, through the tempo changes (120 bpm until the first).
72
+ tempos.sort((a, b) => a.tick - b.tick);
73
+ const toSciTicks = (tick) => {
74
+ let seconds = 0, last = 0, us = 500_000;
75
+ for (const t of tempos) {
76
+ if (t.tick >= tick)
77
+ break;
78
+ seconds += ((t.tick - last) * us) / division / 1e6;
79
+ last = t.tick;
80
+ us = t.usPerQuarter;
81
+ }
82
+ seconds += ((tick - last) * us) / division / 1e6;
83
+ return Math.round(seconds * 60);
84
+ };
85
+ const byChannel = new Map();
86
+ for (const e of events.sort((a, b) => a.tick - b.tick || a.order - b.order)) {
87
+ const channel = e.bytes[0] & 0x0f;
88
+ if (!byChannel.has(channel))
89
+ byChannel.set(channel, []);
90
+ byChannel.get(channel).push({ tick: toSciTicks(e.tick), bytes: e.bytes });
91
+ }
92
+ const music = byChannel.get(CONTROL_CHANNEL);
93
+ if (music) {
94
+ const free = [...Array(15).keys()].find((c) => !byChannel.has(c));
95
+ if (free === undefined)
96
+ throw new AudioFileError("all 16 MIDI channels are used; SCI needs channel 16 for itself");
97
+ byChannel.delete(CONTROL_CHANNEL);
98
+ byChannel.set(free, music);
99
+ }
100
+ const channels = [...byChannel].sort((a, b) => a[0] - b[0]).map(([midiChannel, evs]) => ({ midiChannel, voices: 1, events: evs }));
101
+ if (!channels.length)
102
+ throw new AudioFileError("no notes in this MIDI file");
103
+ return { tracks: [{ device: SoundDevice.GeneralMidi, channels }] };
104
+ }
105
+ /** A PCM WAV file (8- or 16-bit, mono or stereo) as mono 16-bit samples. */
106
+ export function readWav(data) {
107
+ const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
108
+ const tag = (at) => String.fromCharCode(...data.subarray(at, at + 4));
109
+ if (tag(0) !== "RIFF" || tag(8) !== "WAVE")
110
+ throw new AudioFileError("not a WAV file");
111
+ let fmt;
112
+ for (let pos = 12; pos + 8 <= data.length;) {
113
+ const id = tag(pos), size = v.getUint32(pos + 4, true), body = pos + 8;
114
+ if (id === "fmt ") {
115
+ fmt = { format: v.getUint16(body, true), channels: v.getUint16(body + 2, true), rate: v.getUint32(body + 4, true), bits: v.getUint16(body + 14, true) };
116
+ if (fmt.format !== 1)
117
+ throw new AudioFileError(`WAV format ${fmt.format} isn't supported (save as PCM)`);
118
+ if (fmt.bits !== 8 && fmt.bits !== 16)
119
+ throw new AudioFileError(`${fmt.bits}-bit WAV isn't supported (8 or 16)`);
120
+ if (fmt.channels < 1 || fmt.channels > 2)
121
+ throw new AudioFileError(`${fmt.channels} channels: mono or stereo only`);
122
+ if (fmt.rate > 65535)
123
+ throw new AudioFileError(`${fmt.rate} Hz is too high (65,535 at most; 22,050 is plenty)`);
124
+ }
125
+ else if (id === "data") {
126
+ if (!fmt)
127
+ throw new AudioFileError("WAV data before its format");
128
+ const bytes = fmt.bits / 8, frames = Math.floor(Math.min(size, data.length - body) / bytes / fmt.channels);
129
+ const samples = new Int16Array(frames);
130
+ for (let i = 0; i < frames; i++) {
131
+ let sum = 0;
132
+ for (let c = 0; c < fmt.channels; c++) {
133
+ const at = body + (i * fmt.channels + c) * bytes;
134
+ sum += fmt.bits === 16 ? v.getInt16(at, true) : (data[at] - 128) << 8;
135
+ }
136
+ samples[i] = Math.round(sum / fmt.channels);
137
+ }
138
+ return { samples, rate: fmt.rate };
139
+ }
140
+ pos = body + size + (size & 1);
141
+ }
142
+ throw new AudioFileError("WAV file without data");
143
+ }
@@ -0,0 +1,130 @@
1
+ import { SoundDevice, type MidiEvent, type SoundSpec } from "../../packages/sci/src/index.ts";
2
+
3
+ /**
4
+ * Music and sound for games, from the files people make them with: Standard MIDI Files
5
+ * (.mid, from any sequencer) become sound resources; WAV files (PCM) become digital effects.
6
+ */
7
+
8
+ export class AudioFileError extends Error {}
9
+
10
+ const CONTROL_CHANNEL = 15;
11
+
12
+ /**
13
+ * A Standard MIDI File (format 0 or 1) as a sound resource for General MIDI: its tempo
14
+ * changes turned into SCI's 1/60 s ticks, a channel stream per MIDI channel it uses. SCI
15
+ * keeps channel 16 (15 counting from 0) for signals to the scripts, so music there moves to
16
+ * a free channel.
17
+ */
18
+ export function readMidiFile(data: Uint8Array): SoundSpec {
19
+ const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
20
+ const tag = (at: number) => String.fromCharCode(...data.subarray(at, at + 4));
21
+ if (tag(0) !== "MThd") throw new AudioFileError("not a MIDI file (no MThd)");
22
+ const format = v.getUint16(8), trackCount = v.getUint16(10), division = v.getUint16(12);
23
+ if (format > 1) throw new AudioFileError(`MIDI format ${format} isn't supported (save as format 0 or 1)`);
24
+ if (division & 0x8000) throw new AudioFileError("SMPTE-timed MIDI files aren't supported (use ticks per quarter note)");
25
+
26
+ const events: { tick: number; bytes: number[]; order: number }[] = [];
27
+ const tempos: { tick: number; usPerQuarter: number }[] = [];
28
+ let pos = 8 + v.getUint32(4), order = 0;
29
+ for (let t = 0; t < trackCount; t++) {
30
+ if (tag(pos) !== "MTrk") throw new AudioFileError(`track ${t + 1}: expected MTrk`);
31
+ const end = pos + 8 + v.getUint32(pos + 4);
32
+ pos += 8;
33
+ let tick = 0, running = 0;
34
+ const varLen = () => {
35
+ let n = 0, b: number;
36
+ do {
37
+ b = data[pos++]!;
38
+ n = (n << 7) | (b & 0x7f);
39
+ } while (b & 0x80 && pos < end);
40
+ return n;
41
+ };
42
+ while (pos < end) {
43
+ tick += varLen();
44
+ let status = data[pos]!;
45
+ if (status & 0x80) pos++;
46
+ else if (running) status = running;
47
+ else throw new AudioFileError(`track ${t + 1}: data before any status byte`);
48
+ if (status === 0xff) {
49
+ const type = data[pos++]!, len = varLen();
50
+ if (type === 0x51 && len === 3) tempos.push({ tick, usPerQuarter: (data[pos]! << 16) | (data[pos + 1]! << 8) | data[pos + 2]! });
51
+ pos += len;
52
+ if (type === 0x2f) break;
53
+ continue;
54
+ }
55
+ if (status === 0xf0 || status === 0xf7) {
56
+ pos += varLen();
57
+ continue;
58
+ }
59
+ running = status;
60
+ const n = (status & 0xf0) === 0xc0 || (status & 0xf0) === 0xd0 ? 1 : 2;
61
+ events.push({ tick, bytes: [status, ...data.subarray(pos, pos + n)], order: order++ });
62
+ pos += n;
63
+ }
64
+ pos = end;
65
+ }
66
+
67
+ // MIDI ticks to seconds, through the tempo changes (120 bpm until the first).
68
+ tempos.sort((a, b) => a.tick - b.tick);
69
+ const toSciTicks = (tick: number) => {
70
+ let seconds = 0, last = 0, us = 500_000;
71
+ for (const t of tempos) {
72
+ if (t.tick >= tick) break;
73
+ seconds += ((t.tick - last) * us) / division / 1e6;
74
+ last = t.tick;
75
+ us = t.usPerQuarter;
76
+ }
77
+ seconds += ((tick - last) * us) / division / 1e6;
78
+ return Math.round(seconds * 60);
79
+ };
80
+
81
+ const byChannel = new Map<number, MidiEvent[]>();
82
+ for (const e of events.sort((a, b) => a.tick - b.tick || a.order - b.order)) {
83
+ const channel = e.bytes[0]! & 0x0f;
84
+ if (!byChannel.has(channel)) byChannel.set(channel, []);
85
+ byChannel.get(channel)!.push({ tick: toSciTicks(e.tick), bytes: e.bytes });
86
+ }
87
+ const music = byChannel.get(CONTROL_CHANNEL);
88
+ if (music) {
89
+ const free = [...Array(15).keys()].find((c) => !byChannel.has(c));
90
+ if (free === undefined) throw new AudioFileError("all 16 MIDI channels are used; SCI needs channel 16 for itself");
91
+ byChannel.delete(CONTROL_CHANNEL);
92
+ byChannel.set(free, music);
93
+ }
94
+ const channels = [...byChannel].sort((a, b) => a[0] - b[0]).map(([midiChannel, evs]) => ({ midiChannel, voices: 1, events: evs }));
95
+ if (!channels.length) throw new AudioFileError("no notes in this MIDI file");
96
+ return { tracks: [{ device: SoundDevice.GeneralMidi, channels }] };
97
+ }
98
+
99
+ /** A PCM WAV file (8- or 16-bit, mono or stereo) as mono 16-bit samples. */
100
+ export function readWav(data: Uint8Array): { samples: Int16Array; rate: number } {
101
+ const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
102
+ const tag = (at: number) => String.fromCharCode(...data.subarray(at, at + 4));
103
+ if (tag(0) !== "RIFF" || tag(8) !== "WAVE") throw new AudioFileError("not a WAV file");
104
+ let fmt: { format: number; channels: number; rate: number; bits: number } | undefined;
105
+ for (let pos = 12; pos + 8 <= data.length; ) {
106
+ const id = tag(pos), size = v.getUint32(pos + 4, true), body = pos + 8;
107
+ if (id === "fmt ") {
108
+ fmt = { format: v.getUint16(body, true), channels: v.getUint16(body + 2, true), rate: v.getUint32(body + 4, true), bits: v.getUint16(body + 14, true) };
109
+ if (fmt.format !== 1) throw new AudioFileError(`WAV format ${fmt.format} isn't supported (save as PCM)`);
110
+ if (fmt.bits !== 8 && fmt.bits !== 16) throw new AudioFileError(`${fmt.bits}-bit WAV isn't supported (8 or 16)`);
111
+ if (fmt.channels < 1 || fmt.channels > 2) throw new AudioFileError(`${fmt.channels} channels: mono or stereo only`);
112
+ if (fmt.rate > 65535) throw new AudioFileError(`${fmt.rate} Hz is too high (65,535 at most; 22,050 is plenty)`);
113
+ } else if (id === "data") {
114
+ if (!fmt) throw new AudioFileError("WAV data before its format");
115
+ const bytes = fmt.bits / 8, frames = Math.floor(Math.min(size, data.length - body) / bytes / fmt.channels);
116
+ const samples = new Int16Array(frames);
117
+ for (let i = 0; i < frames; i++) {
118
+ let sum = 0;
119
+ for (let c = 0; c < fmt.channels; c++) {
120
+ const at = body + (i * fmt.channels + c) * bytes;
121
+ sum += fmt.bits === 16 ? v.getInt16(at, true) : (data[at]! - 128) << 8;
122
+ }
123
+ samples[i] = Math.round(sum / fmt.channels);
124
+ }
125
+ return { samples, rate: fmt.rate };
126
+ }
127
+ pos = body + size + (size & 1);
128
+ }
129
+ throw new AudioFileError("WAV file without data");
130
+ }
@@ -0,0 +1,46 @@
1
+ import { type ResourceData } from "../../packages/sci/src/index.ts";
2
+ /**
3
+ * Builds a game from its sources, needing nothing from any other game:
4
+ *
5
+ * games/<name>/scripts/<n>.sc scripts (see packages/sci/src/script/compiler.ts), compiled
6
+ * together; they may include files from the game folder
7
+ * games/<name>/scripts/<n>.sca scripts in assembly (packages/sci/src/script/assembly.ts)
8
+ * games/<name>/messages/<n>.msg message files, as text (`pnpm msg` writes the same form)
9
+ * games/<name>/rooms/<n>.room.yaml (+ <n>.yarn) rooms as data (packages/content), compiled
10
+ * for the class library; their Yarn variables are flags
11
+ * numbered in flags.yaml
12
+ * games/<name>/music/<n>.mid music: sound n, from a Standard MIDI File (General MIDI)
13
+ * games/<name>/sounds/<n>.wav digital effects: sound n plays this instead of music
14
+ * games/<name>/resources.ts optional: `export default () => ResourceData[]` for art,
15
+ * music and anything else made by code
16
+ * games/<name>/game.json optional: { "library": false } to build without lib/
17
+ *
18
+ * plus the class library (lib/: its scripts and resources) and font 0, palette 999 and cursor
19
+ * view 999 (tools/game/defaults.ts), unless the game makes its own. Selectors are numbered as the scripts use them, starting with the nine object
20
+ * header slots; vocab 997 lists them and vocab 996 says which script defines each class.
21
+ */
22
+ export interface BuiltGame {
23
+ resources: ResourceData[];
24
+ selectors: string[];
25
+ classes: Map<number, {
26
+ name: string;
27
+ script: number;
28
+ }>;
29
+ /** The assembly compiled from each .sc file, by script number. */
30
+ generated: Map<number, string>;
31
+ /** The globals' names, by number (when script 0 is compiled). */
32
+ globals: string[];
33
+ /** The message text compiled from each YAML room's Yarn, by room. */
34
+ roomMessages: Map<number, string>;
35
+ /** Files that go next to RESOURCE.MAP and RESOURCE.000 (RESOURCE.SFX, the effects). */
36
+ files: Record<string, Uint8Array>;
37
+ /** Likely mistakes that still built, as file:line: message. */
38
+ warnings: string[];
39
+ }
40
+ export declare class GameBuildError extends Error {
41
+ }
42
+ /** sci-ts's class library: scripts every game gets, and the resources they use. */
43
+ export declare const LIBRARY_DIR: string;
44
+ export declare function buildGame(dir: string, options?: {
45
+ library?: boolean;
46
+ }): Promise<BuiltGame>;
@@ -0,0 +1,259 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
10
+ import { basename, dirname, join, relative, resolve } from "node:path";
11
+ import { pathToFileURL } from "node:url";
12
+ import { AsmError, CompileError, GLOBAL_NAMES_VOCAB, OBJECT_HEADER, ResourceType, assemble, compileScripts, kernelNames, messagesFromText, parseScript, resourceKey, writeClassTable, writeEffects, writeMessages, writeSelectorNames, writeSol, writeSound, } from "../../packages/sci/src/index.js";
13
+ import { ContentError, compileRoom } from "../../packages/content/src/index.js";
14
+ import { modFlags } from "../mod-build.js";
15
+ import { AudioFileError, readMidiFile, readWav } from "./audio.js";
16
+ import { defaultResources } from "./defaults.js";
17
+ import { libraryTarget } from "./target.js";
18
+ const HEADER = OBJECT_HEADER;
19
+ export class GameBuildError extends Error {
20
+ }
21
+ /** sci-ts's class library: scripts every game gets, and the resources they use. */
22
+ export const LIBRARY_DIR = resolve(import.meta.dirname, "../../lib");
23
+ export async function buildGame(dir, options = {}) {
24
+ const scriptsDir = join(dir, "scripts");
25
+ if (!existsSync(scriptsDir))
26
+ throw new GameBuildError(`${dir}: no scripts/ folder`);
27
+ const config = existsSync(join(dir, "game.json")) ? JSON.parse(readFileSync(join(dir, "game.json"), "utf8")) : {};
28
+ const library = options.library ?? config.library ?? true;
29
+ const load = (from, re) => readdirSync(from).sort().filter((f) => re.test(f)).map((f) => ({ file: join(from, f), text: readFileSync(join(from, f), "utf8") }));
30
+ const sources = load(scriptsDir, /^\d+\.sca$/);
31
+ const compiled = load(scriptsDir, /^\d+\.sc$/);
32
+ const numberOf = (file) => Number(basename(file).split(".")[0]);
33
+ const numbers = [...sources, ...compiled].map((s) => numberOf(s.file));
34
+ if (!numbers.includes(0))
35
+ throw new GameBuildError(`${dir}: no scripts/0.sc or 0.sca (script 0 exports the game object)`);
36
+ const repeated = numbers.find((n, i) => numbers.indexOf(n) !== i);
37
+ if (repeated !== undefined)
38
+ throw new GameBuildError(`${dir}: script ${repeated} is there twice`);
39
+ if (library) {
40
+ const lib = load(LIBRARY_DIR, /^\d+\.sc$/);
41
+ const clash = lib.find((l) => numbers.includes(numberOf(l.file)));
42
+ if (clash)
43
+ throw new GameBuildError(`${dir}: script ${numberOf(clash.file)} is the library's (${relative(process.cwd(), clash.file)})`);
44
+ compiled.push(...lib);
45
+ }
46
+ const selectors = [...HEADER];
47
+ const classes = new Map();
48
+ const byName = new Map();
49
+ const ctx = {
50
+ selectorNames: selectors,
51
+ kernelNames,
52
+ className: (species) => classes.get(species)?.name,
53
+ propSelectors: (obj) => (obj.isClass ? obj.propSelectors : classes.get(obj.superclass)?.propSelectors),
54
+ classSpecies: (name) => byName.get(name),
55
+ classPropSelectors: (species) => classes.get(species)?.propSelectors,
56
+ classDefaults: (species) => classes.get(species)?.properties,
57
+ newSelector: (name) => selectors.push(name) - 1,
58
+ };
59
+ // Compiled scripts may subclass classes from assembled ones (known once those are built),
60
+ // so the compiler runs inside the same loop.
61
+ const generated = new Map();
62
+ let globals = [];
63
+ const takenSpecies = new Set(sources.flatMap((s) => [...s.text.matchAll(/^\s*class\s+\S+\s+of\s+\S+\s+species\s+(\d+)/gm)].map((m) => Number(m[1]))));
64
+ let compileError;
65
+ const warnings = [];
66
+ const tryCompile = () => {
67
+ if (!compiled.length)
68
+ return [];
69
+ try {
70
+ const out = compileScripts(compiled.map((s) => ({ file: relative(process.cwd(), s.file), text: s.text })), {
71
+ kernelNames,
72
+ takenSpecies,
73
+ // Only when every class is compiled: hand-written scripts' methods can't be seen.
74
+ onWarning: sources.length ? undefined : (w) => warnings.push(`${w.file}:${w.line}: ${w.message}`),
75
+ externalClass: (name) => {
76
+ const species = byName.get(name);
77
+ const c = species === undefined ? undefined : classes.get(species);
78
+ if (!c)
79
+ return undefined;
80
+ const props = c.propSelectors.slice(HEADER.length);
81
+ return { species: c.species, properties: props.map((sel, i) => ({ name: selectors[sel], value: c.properties[HEADER.length + i] })) };
82
+ },
83
+ // Next to the including file, then in the game folder, then in the library.
84
+ include: (name, from) => {
85
+ const path = [join(dirname(from), name), join(dir, name), join(LIBRARY_DIR, name)].find((p) => existsSync(p));
86
+ return path ? readFileSync(path, "utf8") : undefined;
87
+ },
88
+ });
89
+ compiled.length = 0;
90
+ compileError = undefined;
91
+ for (const c of out)
92
+ generated.set(c.number, c.assembly);
93
+ globals = out.find((c) => c.number === 0)?.variables ?? [];
94
+ const rooms = library ? compileRooms() : [];
95
+ return [...out.map((c) => ({ file: c.file, text: c.assembly })), ...rooms];
96
+ }
97
+ catch (e) {
98
+ if (!(e instanceof CompileError))
99
+ throw e;
100
+ compileError = e;
101
+ return [];
102
+ }
103
+ };
104
+ // Rooms as YAML and Yarn: compiled once script 0 is (the target needs its globals).
105
+ const roomMessages = [];
106
+ const roomMessageText = new Map();
107
+ const compileRooms = () => {
108
+ const roomsDir = join(dir, "rooms");
109
+ if (!existsSync(roomsDir))
110
+ return [];
111
+ const target = libraryTarget(globals);
112
+ const flags = modFlags(dir, { content: target });
113
+ const out = [];
114
+ for (const { file, text } of load(roomsDir, /^\d+\.room\.yaml$/)) {
115
+ const n = numberOf(file);
116
+ if (numbers.includes(n))
117
+ throw new GameBuildError(`${relative(process.cwd(), file)}: there's also a scripts/${n} (a room is one or the other)`);
118
+ const yarnFile = join(roomsDir, `${n}.yarn`);
119
+ let room;
120
+ try {
121
+ room = compileRoom(text, existsSync(yarnFile) ? readFileSync(yarnFile, "utf8") : undefined, target, { yaml: relative(process.cwd(), file), yarn: relative(process.cwd(), yarnFile) }, { flag: flags.number });
122
+ }
123
+ catch (e) {
124
+ if (e instanceof ContentError)
125
+ throw new GameBuildError(e.message);
126
+ throw e;
127
+ }
128
+ generated.set(n, room.sca);
129
+ roomMessageText.set(n, room.msg);
130
+ roomMessages.push({ type: ResourceType.Message, number: n, data: writeMessages(messagesFromText(room.msg).file) });
131
+ out.push({ file: relative(process.cwd(), file), text: room.sca });
132
+ }
133
+ flags.save();
134
+ return out;
135
+ };
136
+ // A script can only be assembled once the classes it uses are known, so keep going round
137
+ // until everything is built or nothing more can be.
138
+ const resources = [];
139
+ let pending = [...sources, ...tryCompile()];
140
+ while (pending.length || compiled.length) {
141
+ const failed = [];
142
+ for (const src of pending) {
143
+ try {
144
+ const built = assemble(src.text, ctx);
145
+ if (Number(basename(src.file).split(".")[0]) !== built.number)
146
+ throw new GameBuildError(`${src.file} says it is script ${built.number}`);
147
+ resources.push({ type: ResourceType.Script, number: built.number, data: built.code }, { type: ResourceType.Heap, number: built.number, data: built.heap });
148
+ for (const o of parseScript(built.number, built.code, built.heap).objects) {
149
+ if (!o.isClass)
150
+ continue;
151
+ const other = classes.get(o.species);
152
+ if (other)
153
+ throw new GameBuildError(`${src.file}: class ${o.name} has species ${o.species}, already taken by ${other.name} (script ${other.script})`);
154
+ if (byName.has(o.name))
155
+ throw new GameBuildError(`${src.file}: class ${o.name} is defined twice`);
156
+ classes.set(o.species, { ...o, script: built.number });
157
+ byName.set(o.name, o.species);
158
+ }
159
+ }
160
+ catch (e) {
161
+ if (!(e instanceof AsmError))
162
+ throw e;
163
+ failed.push({ ...src, error: e });
164
+ }
165
+ }
166
+ const more = tryCompile();
167
+ if (failed.length === pending.length && !more.length) {
168
+ if (compileError)
169
+ throw new GameBuildError(compileError.message);
170
+ throw new GameBuildError(failed.map((f) => `${f.file}: ${f.error.message}`).join("\n"));
171
+ }
172
+ pending = [...failed, ...more];
173
+ }
174
+ const classTable = Array.from({ length: Math.max(-1, ...classes.keys()) + 1 }, (_, species) => classes.get(species)?.script ?? 0);
175
+ resources.push({ type: ResourceType.Vocab, number: 997, data: writeSelectorNames(selectors) }, { type: ResourceType.Vocab, number: 996, data: writeClassTable(classTable) });
176
+ // The globals' names, for tools reading the running game (the player's room links, the
177
+ // editor, the explorer): sci-ts's own vocab, in the selector list format.
178
+ if (globals.length)
179
+ resources.push({ type: ResourceType.Vocab, number: GLOBAL_NAMES_VOCAB, data: writeSelectorNames(globals) });
180
+ // Messages: the rooms', then those written as text.
181
+ resources.push(...roomMessages);
182
+ const messagesDir = join(dir, "messages");
183
+ if (existsSync(messagesDir)) {
184
+ for (const { file, text } of load(messagesDir, /^\d+\.msg$/)) {
185
+ let parsed;
186
+ try {
187
+ parsed = messagesFromText(text);
188
+ }
189
+ catch (e) {
190
+ throw new GameBuildError(`${relative(process.cwd(), file)}: ${e.message}`);
191
+ }
192
+ if (parsed.number !== numberOf(file))
193
+ throw new GameBuildError(`${file} says it is message file ${parsed.number}`);
194
+ resources.push({ type: ResourceType.Message, number: parsed.number, data: writeMessages(parsed.file) });
195
+ }
196
+ }
197
+ // Music (MIDI files) and digital effects (WAV files).
198
+ const audioFile = (file, read) => {
199
+ try {
200
+ return read();
201
+ }
202
+ catch (e) {
203
+ if (e instanceof AudioFileError)
204
+ throw new GameBuildError(`${relative(process.cwd(), file)}: ${e.message}`);
205
+ throw e;
206
+ }
207
+ };
208
+ const musicDir = join(dir, "music");
209
+ if (existsSync(musicDir)) {
210
+ for (const f of readdirSync(musicDir).filter((x) => /^\d+\.midi?$/i.test(x)).sort()) {
211
+ const file = join(musicDir, f);
212
+ const spec = audioFile(file, () => readMidiFile(new Uint8Array(readFileSync(file))));
213
+ resources.push({ type: ResourceType.Sound, number: numberOf(file), data: writeSound(spec) });
214
+ }
215
+ }
216
+ const files = {};
217
+ const soundsDir = join(dir, "sounds");
218
+ if (existsSync(soundsDir)) {
219
+ const clips = readdirSync(soundsDir).filter((x) => /^\d+\.wav$/i.test(x)).sort().map((f) => {
220
+ const file = join(soundsDir, f);
221
+ const { samples, rate } = audioFile(file, () => readWav(new Uint8Array(readFileSync(file))));
222
+ return { number: numberOf(file), sol: writeSol(samples, rate) };
223
+ });
224
+ if (clips.length) {
225
+ const { sfx, map } = writeEffects(clips);
226
+ files["RESOURCE.SFX"] = sfx;
227
+ resources.push({ type: ResourceType.Map, number: 65535, data: map });
228
+ }
229
+ }
230
+ // Resources made in code: the game's, then the library's and the defaults for what's left.
231
+ const made = async (path) => {
232
+ if (!existsSync(path))
233
+ return [];
234
+ const mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(resolve(path)).href)));
235
+ return mod.default();
236
+ };
237
+ resources.push(...(await made(join(dir, "resources.ts"))));
238
+ const have = new Set(resources.map(resourceKey));
239
+ // The library's resources are compiled in the npm package (resources.js), source here.
240
+ const libraryResources = ["resources.js", "resources.ts"].map((f) => join(LIBRARY_DIR, f)).find((f) => existsSync(f));
241
+ const fallback = [...(library ? await made(libraryResources) : []), ...defaultResources()];
242
+ for (const r of fallback)
243
+ if (!have.has(resourceKey(r)))
244
+ (resources.push(r), have.add(resourceKey(r)));
245
+ const keys = resources.map(resourceKey);
246
+ const twice = keys.find((k, i) => keys.indexOf(k) !== i);
247
+ if (twice)
248
+ throw new GameBuildError(`${dir}: resource ${twice} is made twice`);
249
+ return {
250
+ resources,
251
+ selectors,
252
+ classes: new Map([...classes].map(([species, c]) => [species, { name: c.name, script: c.script }])),
253
+ generated,
254
+ globals,
255
+ roomMessages: roomMessageText,
256
+ files,
257
+ warnings: [...new Set(warnings)],
258
+ };
259
+ }