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,127 @@
1
+ import { NULL, bool, segmentOf, toSigned } from "../value.js";
2
+ const list = (vm, v) => {
3
+ const l = vm.memory.list(v);
4
+ if (!l)
5
+ throw new Error(`Not a list: ${v.toString(16)}`);
6
+ return l;
7
+ };
8
+ function unlink(l, n) {
9
+ if (n.prev)
10
+ n.prev.next = n.next;
11
+ else
12
+ l.first = n.next;
13
+ if (n.next)
14
+ n.next.prev = n.prev;
15
+ else
16
+ l.last = n.prev;
17
+ n.prev = n.next = n.list = undefined;
18
+ }
19
+ function nodes(l) {
20
+ const out = [];
21
+ for (let n = l.first; n; n = n.next)
22
+ out.push(n);
23
+ return out;
24
+ }
25
+ /**
26
+ * Doubly linked lists of nodes (value + key). The system scripts' List/Set/Collection
27
+ * classes are thin wrappers around these.
28
+ */
29
+ export const listKernels = {
30
+ NewList: (vm) => vm.memory.newList().address,
31
+ // As in SSCI, disposing a list frees its nodes, and DeleteKey frees the node it removes.
32
+ DisposeList: (vm, [l = 0]) => {
33
+ const lst = vm.memory.list(l);
34
+ if (!lst)
35
+ return;
36
+ for (const n of nodes(lst)) {
37
+ n.list = undefined;
38
+ vm.memory.free(segmentOf(n.address));
39
+ }
40
+ lst.first = lst.last = undefined;
41
+ vm.memory.free(segmentOf(l));
42
+ },
43
+ // SCI32: key defaults to the value.
44
+ NewNode: (vm, [value = 0, key]) => vm.memory.newNode(value, key ?? value).address,
45
+ FirstNode: (vm, [l = 0]) => (l ? list(vm, l).first?.address ?? NULL : NULL),
46
+ LastNode: (vm, [l = 0]) => (l ? list(vm, l).last?.address ?? NULL : NULL),
47
+ EmptyList: (vm, [l = 0]) => bool(!l || !list(vm, l).first),
48
+ NextNode: (vm, [n = 0]) => vm.memory.node(n)?.next?.address ?? NULL,
49
+ PrevNode: (vm, [n = 0]) => vm.memory.node(n)?.prev?.address ?? NULL,
50
+ NodeValue: (vm, [n = 0]) => vm.memory.node(n)?.value ?? NULL,
51
+ AddToEnd: (vm, [l = 0, n = 0, key]) => {
52
+ const lst = list(vm, l), node = vm.memory.node(n);
53
+ if (key !== undefined)
54
+ node.key = key;
55
+ node.list = lst;
56
+ node.prev = lst.last;
57
+ node.next = undefined;
58
+ if (lst.last)
59
+ lst.last.next = node;
60
+ else
61
+ lst.first = node;
62
+ lst.last = node;
63
+ return n;
64
+ },
65
+ AddToFront: (vm, [l = 0, n = 0, key]) => {
66
+ const lst = list(vm, l), node = vm.memory.node(n);
67
+ if (key !== undefined)
68
+ node.key = key;
69
+ node.list = lst;
70
+ node.next = lst.first;
71
+ node.prev = undefined;
72
+ if (lst.first)
73
+ lst.first.prev = node;
74
+ else
75
+ lst.last = node;
76
+ lst.first = node;
77
+ return n;
78
+ },
79
+ AddAfter: (vm, [l = 0, after = 0, n = 0, key]) => {
80
+ const lst = list(vm, l), prev = vm.memory.node(after), node = vm.memory.node(n);
81
+ if (key !== undefined)
82
+ node.key = key;
83
+ if (!prev)
84
+ return listKernels.AddToFront(vm, [l, n]);
85
+ node.list = lst;
86
+ node.prev = prev;
87
+ node.next = prev.next;
88
+ if (prev.next)
89
+ prev.next.prev = node;
90
+ else
91
+ lst.last = node;
92
+ prev.next = node;
93
+ return n;
94
+ },
95
+ FindKey: (vm, [l = 0, key = 0]) => nodes(list(vm, l)).find((n) => n.key === key)?.address ?? NULL,
96
+ DeleteKey: (vm, [l = 0, key = 0]) => {
97
+ const lst = list(vm, l);
98
+ const n = nodes(lst).find((x) => x.key === key);
99
+ if (!n)
100
+ return 0;
101
+ unlink(lst, n);
102
+ vm.memory.free(segmentOf(n.address));
103
+ return 1;
104
+ },
105
+ ListAt: (vm, [l = 0, index = 0]) => nodes(list(vm, l))[toSigned(index)]?.value ?? NULL,
106
+ ListIndexOf: (vm, [l = 0, value = 0]) => nodes(list(vm, l)).findIndex((n) => n.value === value) & 0xffff,
107
+ // Iteration kernels send `selector` (+args) to each element. They're generators so the
108
+ // sends run on the VM's frame stack. Snapshot first so an element can remove itself.
109
+ *ListEachElementDo(vm, [l = 0, selector = 0, ...args]) {
110
+ for (const n of nodes(list(vm, l)))
111
+ if (n.list)
112
+ yield { object: n.value, selector, args };
113
+ return 0;
114
+ },
115
+ *ListFirstTrue(vm, [l = 0, selector = 0, ...args]) {
116
+ for (const n of nodes(list(vm, l)))
117
+ if (yield { object: n.value, selector, args })
118
+ return n.value;
119
+ return NULL;
120
+ },
121
+ *ListAllTrue(vm, [l = 0, selector = 0, ...args]) {
122
+ for (const n of nodes(list(vm, l)))
123
+ if (!(yield { object: n.value, selector, args }))
124
+ return 0;
125
+ return 1;
126
+ },
127
+ };
@@ -0,0 +1,111 @@
1
+ import type { ListNode, SciList } from "../memory.ts";
2
+ import type { KernelFn, SendRequest, Vm } from "../vm.ts";
3
+ import { NULL, bool, segmentOf, toSigned, type Value } from "../value.ts";
4
+
5
+ const list = (vm: Vm, v: Value): SciList => {
6
+ const l = vm.memory.list(v);
7
+ if (!l) throw new Error(`Not a list: ${v.toString(16)}`);
8
+ return l;
9
+ };
10
+
11
+ function unlink(l: SciList, n: ListNode) {
12
+ if (n.prev) n.prev.next = n.next;
13
+ else l.first = n.next;
14
+ if (n.next) n.next.prev = n.prev;
15
+ else l.last = n.prev;
16
+ n.prev = n.next = n.list = undefined;
17
+ }
18
+
19
+ function nodes(l: SciList): ListNode[] {
20
+ const out: ListNode[] = [];
21
+ for (let n = l.first; n; n = n.next) out.push(n);
22
+ return out;
23
+ }
24
+
25
+ /**
26
+ * Doubly linked lists of nodes (value + key). The system scripts' List/Set/Collection
27
+ * classes are thin wrappers around these.
28
+ */
29
+ export const listKernels: Record<string, KernelFn> = {
30
+ NewList: (vm) => vm.memory.newList().address,
31
+ // As in SSCI, disposing a list frees its nodes, and DeleteKey frees the node it removes.
32
+ DisposeList: (vm, [l = 0]) => {
33
+ const lst = vm.memory.list(l);
34
+ if (!lst) return;
35
+ for (const n of nodes(lst)) {
36
+ n.list = undefined;
37
+ vm.memory.free(segmentOf(n.address));
38
+ }
39
+ lst.first = lst.last = undefined;
40
+ vm.memory.free(segmentOf(l));
41
+ },
42
+ // SCI32: key defaults to the value.
43
+ NewNode: (vm, [value = 0, key]) => vm.memory.newNode(value, key ?? value).address,
44
+ FirstNode: (vm, [l = 0]) => (l ? list(vm, l).first?.address ?? NULL : NULL),
45
+ LastNode: (vm, [l = 0]) => (l ? list(vm, l).last?.address ?? NULL : NULL),
46
+ EmptyList: (vm, [l = 0]) => bool(!l || !list(vm, l).first),
47
+ NextNode: (vm, [n = 0]) => vm.memory.node(n)?.next?.address ?? NULL,
48
+ PrevNode: (vm, [n = 0]) => vm.memory.node(n)?.prev?.address ?? NULL,
49
+ NodeValue: (vm, [n = 0]) => vm.memory.node(n)?.value ?? NULL,
50
+
51
+ AddToEnd: (vm, [l = 0, n = 0, key]) => {
52
+ const lst = list(vm, l), node = vm.memory.node(n)!;
53
+ if (key !== undefined) node.key = key;
54
+ node.list = lst;
55
+ node.prev = lst.last;
56
+ node.next = undefined;
57
+ if (lst.last) lst.last.next = node;
58
+ else lst.first = node;
59
+ lst.last = node;
60
+ return n;
61
+ },
62
+ AddToFront: (vm, [l = 0, n = 0, key]) => {
63
+ const lst = list(vm, l), node = vm.memory.node(n)!;
64
+ if (key !== undefined) node.key = key;
65
+ node.list = lst;
66
+ node.next = lst.first;
67
+ node.prev = undefined;
68
+ if (lst.first) lst.first.prev = node;
69
+ else lst.last = node;
70
+ lst.first = node;
71
+ return n;
72
+ },
73
+ AddAfter: (vm, [l = 0, after = 0, n = 0, key]) => {
74
+ const lst = list(vm, l), prev = vm.memory.node(after), node = vm.memory.node(n)!;
75
+ if (key !== undefined) node.key = key;
76
+ if (!prev) return listKernels.AddToFront!(vm, [l, n]);
77
+ node.list = lst;
78
+ node.prev = prev;
79
+ node.next = prev.next;
80
+ if (prev.next) prev.next.prev = node;
81
+ else lst.last = node;
82
+ prev.next = node;
83
+ return n;
84
+ },
85
+ FindKey: (vm, [l = 0, key = 0]) => nodes(list(vm, l)).find((n) => n.key === key)?.address ?? NULL,
86
+ DeleteKey: (vm, [l = 0, key = 0]) => {
87
+ const lst = list(vm, l);
88
+ const n = nodes(lst).find((x) => x.key === key);
89
+ if (!n) return 0;
90
+ unlink(lst, n);
91
+ vm.memory.free(segmentOf(n.address));
92
+ return 1;
93
+ },
94
+ ListAt: (vm, [l = 0, index = 0]) => nodes(list(vm, l))[toSigned(index)]?.value ?? NULL,
95
+ ListIndexOf: (vm, [l = 0, value = 0]) => nodes(list(vm, l)).findIndex((n) => n.value === value) & 0xffff,
96
+
97
+ // Iteration kernels send `selector` (+args) to each element. They're generators so the
98
+ // sends run on the VM's frame stack. Snapshot first so an element can remove itself.
99
+ *ListEachElementDo(vm: Vm, [l = 0, selector = 0, ...args]: Value[]): Generator<SendRequest, Value, Value> {
100
+ for (const n of nodes(list(vm, l))) if (n.list) yield { object: n.value, selector, args };
101
+ return 0;
102
+ },
103
+ *ListFirstTrue(vm: Vm, [l = 0, selector = 0, ...args]: Value[]): Generator<SendRequest, Value, Value> {
104
+ for (const n of nodes(list(vm, l))) if (yield { object: n.value, selector, args }) return n.value;
105
+ return NULL;
106
+ },
107
+ *ListAllTrue(vm: Vm, [l = 0, selector = 0, ...args]: Value[]): Generator<SendRequest, Value, Value> {
108
+ for (const n of nodes(list(vm, l))) if (!(yield { object: n.value, selector, args })) return 0;
109
+ return 1;
110
+ },
111
+ };
@@ -0,0 +1,7 @@
1
+ import type { KernelFn } from "../vm.ts";
2
+ /**
3
+ * Actor movement. A Motion object ("mover") walks its client toward (x, y) using an
4
+ * integer Bresenham line, one step of (xStep, yStep) per update: the original
5
+ * interpreter's algorithm, as ScummVM's documentation describes it, written afresh here.
6
+ */
7
+ export declare const motionKernels: Record<string, KernelFn>;
@@ -0,0 +1,204 @@
1
+ import { findPath, contains } from "../../motion/pathfinding.js";
2
+ import { bool, fromInt, offsetOf, toSigned } from "../value.js";
3
+ import { stringHelpers } from "./arrays.js";
4
+ import { graphics } from "./graphics.js";
5
+ const SIGNAL_HIT_OBSTACLE = 0x0400;
6
+ const SIGNAL_FIXED_LOOP = 0x0800;
7
+ const POLY_END = 0x7777;
8
+ const get = (vm, obj, name) => toSigned(vm.getProp(obj, name) ?? 0);
9
+ /** Signals that exclude an actor from actor-vs-actor collisions (don't-restore, hidden, ignore-actors). */
10
+ const COLLISION_EXEMPT = 0x0004 | 0x0008 | 0x4000;
11
+ /** Reads `count` int16 values from an array, an IntArray-style object, or static heap data. */
12
+ function readInts(vm, ref, count) {
13
+ const array = stringHelpers.resolve(vm, ref);
14
+ if (array)
15
+ return array.data.slice(0, count).map(toSigned);
16
+ const seg = vm.memory.segment(ref);
17
+ if (seg?.kind === "script") {
18
+ const heap = seg.script.script.heap;
19
+ const v = new DataView(heap.buffer, heap.byteOffset, heap.byteLength);
20
+ return Array.from({ length: count }, (_, i) => v.getInt16(offsetOf(ref) + i * 2, true));
21
+ }
22
+ return Array.from({ length: count }, (_, i) => toSigned(vm.memory.read(ref + i * 2)));
23
+ }
24
+ function readPolygon(vm, obj) {
25
+ if (!vm.memory.object(obj))
26
+ return undefined;
27
+ const size = get(vm, obj, "size");
28
+ const ints = readInts(vm, vm.getProp(obj, "points") ?? 0, size * 2);
29
+ const points = Array.from({ length: size }, (_, i) => ({ x: ints[i * 2] ?? 0, y: ints[i * 2 + 1] ?? 0 }));
30
+ return size >= 3 ? { type: get(vm, obj, "type"), points } : undefined;
31
+ }
32
+ function readPolygons(vm, list) {
33
+ const out = [];
34
+ for (let n = vm.memory.list(list)?.first; n; n = n.next) {
35
+ const poly = readPolygon(vm, n.value);
36
+ if (poly)
37
+ out.push(poly);
38
+ }
39
+ return out;
40
+ }
41
+ const set = (vm, obj, name, v) => vm.setProp(obj, name, fromInt(v));
42
+ /**
43
+ * Actor movement. A Motion object ("mover") walks its client toward (x, y) using an
44
+ * integer Bresenham line, one step of (xStep, yStep) per update: the original
45
+ * interpreter's algorithm, as ScummVM's documentation describes it, written afresh here.
46
+ */
47
+ export const motionKernels = {
48
+ InitBresen: (vm, [mover = 0, stepFactor = 1]) => {
49
+ const client = vm.getProp(mover, "client");
50
+ let xStep = get(vm, client, "xStep") * stepFactor;
51
+ const yStep = get(vm, client, "yStep") * stepFactor;
52
+ let step = xStep < yStep ? yStep * 2 : xStep * 2;
53
+ const deltaX = get(vm, mover, "x") - get(vm, client, "x");
54
+ const deltaY = get(vm, mover, "y") - get(vm, client, "y");
55
+ let dx = 0, dy = 0, i1 = 0, i2 = 0, di = 0, incr = 0, xAxis = 0;
56
+ for (;;) {
57
+ dx = xStep;
58
+ dy = yStep;
59
+ incr = 1;
60
+ if (Math.abs(deltaX) >= Math.abs(deltaY)) {
61
+ xAxis = 1;
62
+ if (deltaX < 0)
63
+ dx = -dx;
64
+ dy = deltaX ? Math.trunc((dx * deltaY) / deltaX) : 0;
65
+ i1 = (dx * deltaY - dy * deltaX) * 2;
66
+ if (deltaY < 0)
67
+ (incr = -1), (i1 = -i1);
68
+ i2 = i1 - deltaX * 2;
69
+ di = i1 - deltaX;
70
+ if (deltaX < 0)
71
+ (i1 = -i1), (i2 = -i2), (di = -di);
72
+ }
73
+ else {
74
+ xAxis = 0;
75
+ if (deltaY < 0)
76
+ dy = -dy;
77
+ dx = deltaY ? Math.trunc((dy * deltaX) / deltaY) : 0;
78
+ i1 = (dy * deltaX - dx * deltaY) * 2;
79
+ if (deltaX < 0)
80
+ (incr = -1), (i1 = -i1);
81
+ i2 = i1 - deltaY * 2;
82
+ di = i1 - deltaY;
83
+ if (deltaY < 0)
84
+ (i1 = -i1), (i2 = -i2), (di = -di);
85
+ break;
86
+ }
87
+ // Along x, shrink xStep until the y step fits.
88
+ if (xStep <= yStep || !xStep || yStep >= Math.abs(dy + incr))
89
+ break;
90
+ if (!--step)
91
+ break;
92
+ xStep--;
93
+ }
94
+ set(vm, mover, "dx", dx);
95
+ set(vm, mover, "dy", dy);
96
+ set(vm, mover, "b-i1", i1);
97
+ set(vm, mover, "b-i2", i2);
98
+ set(vm, mover, "b-di", di);
99
+ set(vm, mover, "b-incr", incr);
100
+ set(vm, mover, "b-xAxis", xAxis);
101
+ },
102
+ /**
103
+ * One Bresenham step of the mover's client. SCI2 differs from SCI0–1.1 in two ways the
104
+ * scripts depend on: `Motion::doit` already throttles by comparing `b-moveCnt` (a game-time
105
+ * stamp) to `moveSpeed`, so the kernel must not touch it; and `doit` ignores our return
106
+ * value, so on arrival the kernel itself sends `moveDone` to the mover.
107
+ */
108
+ *DoBresen(vm, [mover = 0]) {
109
+ const client = vm.getProp(mover, "client");
110
+ set(vm, client, "signal", get(vm, client, "signal") & ~SIGNAL_HIT_OBSTACLE);
111
+ const toX = get(vm, mover, "x"), toY = get(vm, mover, "y");
112
+ const xAxis = get(vm, mover, "b-xAxis"), dx = get(vm, mover, "dx"), dy = get(vm, mover, "dy");
113
+ const incr = get(vm, mover, "b-incr"), i1 = get(vm, mover, "b-i1"), i2 = get(vm, mover, "b-i2");
114
+ let di = get(vm, mover, "b-di");
115
+ const origX = get(vm, client, "x"), origY = get(vm, client, "y"), origDi = di;
116
+ let x = origX, y = origY;
117
+ if ((xAxis && Math.abs(toX - x) < Math.abs(dx)) || (!xAxis && Math.abs(toY - y) < Math.abs(dy))) {
118
+ x = toX;
119
+ y = toY;
120
+ }
121
+ else {
122
+ x += dx;
123
+ y += dy;
124
+ if (di < 0)
125
+ di += i1;
126
+ else {
127
+ di += i2;
128
+ if (xAxis)
129
+ y += incr;
130
+ else
131
+ x += incr;
132
+ }
133
+ }
134
+ set(vm, client, "x", x);
135
+ set(vm, client, "y", y);
136
+ const cantBeHere = yield { object: client, selector: vm.selector("cantBeHere"), args: [] };
137
+ if (cantBeHere) {
138
+ set(vm, client, "x", origX);
139
+ set(vm, client, "y", origY);
140
+ di = origDi;
141
+ set(vm, client, "signal", get(vm, client, "signal") | SIGNAL_HIT_OBSTACLE);
142
+ }
143
+ set(vm, mover, "b-di", di);
144
+ const completed = get(vm, client, "x") === toX && get(vm, client, "y") === toY;
145
+ if (completed)
146
+ yield { object: mover, selector: vm.selector("moveDone"), args: [] };
147
+ return completed ? 1 : 0;
148
+ },
149
+ DirLoop: (vm, [obj = 0, angle = 0]) => {
150
+ if (get(vm, obj, "signal") & SIGNAL_FIXED_LOOP)
151
+ return;
152
+ const loops = graphics(vm).view(get(vm, obj, "view"))?.loops.length ?? 0;
153
+ let loop;
154
+ if (loops >= 4) {
155
+ if (angle > 315 || angle < 45)
156
+ loop = 3; // north
157
+ else if (angle < 136)
158
+ loop = 0; // east
159
+ else if (angle < 226)
160
+ loop = 2; // south
161
+ else
162
+ loop = 1; // west
163
+ }
164
+ else
165
+ loop = angle > 180 ? 1 : 0;
166
+ set(vm, obj, "loop", loop);
167
+ },
168
+ /**
169
+ * Actor-vs-actor collision: can `obj` stand where it is, given the other actors' base
170
+ * rects in `list`? (Walls are handled by pathfinding, not here.) Returns 1 if blocked.
171
+ */
172
+ CantBeHere: (vm, [obj = 0, list = 0]) => {
173
+ if (get(vm, obj, "signal") & COLLISION_EXEMPT)
174
+ return 0;
175
+ const r = (o) => ({ l: get(vm, o, "brLeft"), t: get(vm, o, "brTop"), r: get(vm, o, "brRight"), b: get(vm, o, "brBottom") });
176
+ const me = r(obj);
177
+ for (let n = vm.memory.list(list)?.first; n; n = n.next) {
178
+ const other = n.value;
179
+ if (other === obj || !vm.memory.object(other) || get(vm, other, "signal") & COLLISION_EXEMPT)
180
+ continue;
181
+ const o = r(other);
182
+ // Strict overlap: touching isn't a collision (as in the original).
183
+ if (o.r > me.l && o.l < me.r && o.b > me.t && o.t < me.b)
184
+ return 1;
185
+ }
186
+ return 0;
187
+ },
188
+ /** AvoidPath(x1, y1, x2, y2, polygonList, opt) → int16 array of points ending in 0x7777. */
189
+ AvoidPath: (vm, [x1 = 0, y1 = 0, x2 = 0, y2 = 0, list = 0]) => {
190
+ const start = { x: toSigned(x1), y: toSigned(y1) };
191
+ const end = { x: toSigned(x2), y: toSigned(y2) };
192
+ const path = list ? findPath(start, end, readPolygons(vm, list)) : [start, end];
193
+ const ref = vm.memory.newArray("int16", 0);
194
+ vm.memory.array(ref).data = [...path.flatMap((p) => [fromInt(Math.round(p.x)), fromInt(Math.round(p.y))]), POLY_END, POLY_END];
195
+ return ref;
196
+ },
197
+ InPolygon: (vm, [x = 0, y = 0, polygon = 0]) => {
198
+ const poly = readPolygon(vm, polygon);
199
+ return bool(!!poly && contains(poly, { x: toSigned(x), y: toSigned(y) }) !== "outside");
200
+ },
201
+ // TODO: merge a dynamic obstacle with overlapping barred polygons.
202
+ MergePoly: (_vm, [polygon = 0]) => polygon,
203
+ SetJump: () => 0,
204
+ };
@@ -0,0 +1,194 @@
1
+ import { findPath, contains, type Polygon } from "../../motion/pathfinding.ts";
2
+ import type { KernelFn, SendRequest, Vm } from "../vm.ts";
3
+ import { bool, fromInt, offsetOf, toSigned, type Value } from "../value.ts";
4
+ import { stringHelpers } from "./arrays.ts";
5
+ import { graphics } from "./graphics.ts";
6
+
7
+ const SIGNAL_HIT_OBSTACLE = 0x0400;
8
+ const SIGNAL_FIXED_LOOP = 0x0800;
9
+ const POLY_END = 0x7777;
10
+
11
+ const get = (vm: Vm, obj: Value, name: string) => toSigned(vm.getProp(obj, name) ?? 0);
12
+
13
+ /** Signals that exclude an actor from actor-vs-actor collisions (don't-restore, hidden, ignore-actors). */
14
+ const COLLISION_EXEMPT = 0x0004 | 0x0008 | 0x4000;
15
+
16
+ /** Reads `count` int16 values from an array, an IntArray-style object, or static heap data. */
17
+ function readInts(vm: Vm, ref: Value, count: number): number[] {
18
+ const array = stringHelpers.resolve(vm, ref);
19
+ if (array) return array.data.slice(0, count).map(toSigned);
20
+ const seg = vm.memory.segment(ref);
21
+ if (seg?.kind === "script") {
22
+ const heap = seg.script.script.heap;
23
+ const v = new DataView(heap.buffer, heap.byteOffset, heap.byteLength);
24
+ return Array.from({ length: count }, (_, i) => v.getInt16(offsetOf(ref) + i * 2, true));
25
+ }
26
+ return Array.from({ length: count }, (_, i) => toSigned(vm.memory.read(ref + i * 2)));
27
+ }
28
+
29
+ function readPolygon(vm: Vm, obj: Value): Polygon | undefined {
30
+ if (!vm.memory.object(obj)) return undefined;
31
+ const size = get(vm, obj, "size");
32
+ const ints = readInts(vm, vm.getProp(obj, "points") ?? 0, size * 2);
33
+ const points = Array.from({ length: size }, (_, i) => ({ x: ints[i * 2] ?? 0, y: ints[i * 2 + 1] ?? 0 }));
34
+ return size >= 3 ? { type: get(vm, obj, "type"), points } : undefined;
35
+ }
36
+
37
+ function readPolygons(vm: Vm, list: Value): Polygon[] {
38
+ const out: Polygon[] = [];
39
+ for (let n = vm.memory.list(list)?.first; n; n = n.next) {
40
+ const poly = readPolygon(vm, n.value);
41
+ if (poly) out.push(poly);
42
+ }
43
+ return out;
44
+ }
45
+ const set = (vm: Vm, obj: Value, name: string, v: number) => vm.setProp(obj, name, fromInt(v));
46
+
47
+ /**
48
+ * Actor movement. A Motion object ("mover") walks its client toward (x, y) using an
49
+ * integer Bresenham line, one step of (xStep, yStep) per update: the original
50
+ * interpreter's algorithm, as ScummVM's documentation describes it, written afresh here.
51
+ */
52
+ export const motionKernels: Record<string, KernelFn> = {
53
+ InitBresen: (vm, [mover = 0, stepFactor = 1]) => {
54
+ const client = vm.getProp(mover, "client")!;
55
+ let xStep = get(vm, client, "xStep") * stepFactor;
56
+ const yStep = get(vm, client, "yStep") * stepFactor;
57
+ let step = xStep < yStep ? yStep * 2 : xStep * 2;
58
+ const deltaX = get(vm, mover, "x") - get(vm, client, "x");
59
+ const deltaY = get(vm, mover, "y") - get(vm, client, "y");
60
+ let dx = 0, dy = 0, i1 = 0, i2 = 0, di = 0, incr = 0, xAxis = 0;
61
+
62
+ for (;;) {
63
+ dx = xStep;
64
+ dy = yStep;
65
+ incr = 1;
66
+ if (Math.abs(deltaX) >= Math.abs(deltaY)) {
67
+ xAxis = 1;
68
+ if (deltaX < 0) dx = -dx;
69
+ dy = deltaX ? Math.trunc((dx * deltaY) / deltaX) : 0;
70
+ i1 = (dx * deltaY - dy * deltaX) * 2;
71
+ if (deltaY < 0) (incr = -1), (i1 = -i1);
72
+ i2 = i1 - deltaX * 2;
73
+ di = i1 - deltaX;
74
+ if (deltaX < 0) (i1 = -i1), (i2 = -i2), (di = -di);
75
+ } else {
76
+ xAxis = 0;
77
+ if (deltaY < 0) dy = -dy;
78
+ dx = deltaY ? Math.trunc((dy * deltaX) / deltaY) : 0;
79
+ i1 = (dy * deltaX - dx * deltaY) * 2;
80
+ if (deltaX < 0) (incr = -1), (i1 = -i1);
81
+ i2 = i1 - deltaY * 2;
82
+ di = i1 - deltaY;
83
+ if (deltaY < 0) (i1 = -i1), (i2 = -i2), (di = -di);
84
+ break;
85
+ }
86
+ // Along x, shrink xStep until the y step fits.
87
+ if (xStep <= yStep || !xStep || yStep >= Math.abs(dy + incr)) break;
88
+ if (!--step) break;
89
+ xStep--;
90
+ }
91
+ set(vm, mover, "dx", dx);
92
+ set(vm, mover, "dy", dy);
93
+ set(vm, mover, "b-i1", i1);
94
+ set(vm, mover, "b-i2", i2);
95
+ set(vm, mover, "b-di", di);
96
+ set(vm, mover, "b-incr", incr);
97
+ set(vm, mover, "b-xAxis", xAxis);
98
+ },
99
+
100
+ /**
101
+ * One Bresenham step of the mover's client. SCI2 differs from SCI0–1.1 in two ways the
102
+ * scripts depend on: `Motion::doit` already throttles by comparing `b-moveCnt` (a game-time
103
+ * stamp) to `moveSpeed`, so the kernel must not touch it; and `doit` ignores our return
104
+ * value, so on arrival the kernel itself sends `moveDone` to the mover.
105
+ */
106
+ *DoBresen(vm: Vm, [mover = 0]: Value[]): Generator<SendRequest, Value, Value> {
107
+ const client = vm.getProp(mover, "client")!;
108
+ set(vm, client, "signal", get(vm, client, "signal") & ~SIGNAL_HIT_OBSTACLE);
109
+
110
+ const toX = get(vm, mover, "x"), toY = get(vm, mover, "y");
111
+ const xAxis = get(vm, mover, "b-xAxis"), dx = get(vm, mover, "dx"), dy = get(vm, mover, "dy");
112
+ const incr = get(vm, mover, "b-incr"), i1 = get(vm, mover, "b-i1"), i2 = get(vm, mover, "b-i2");
113
+ let di = get(vm, mover, "b-di");
114
+ const origX = get(vm, client, "x"), origY = get(vm, client, "y"), origDi = di;
115
+ let x = origX, y = origY;
116
+
117
+ if ((xAxis && Math.abs(toX - x) < Math.abs(dx)) || (!xAxis && Math.abs(toY - y) < Math.abs(dy))) {
118
+ x = toX;
119
+ y = toY;
120
+ } else {
121
+ x += dx;
122
+ y += dy;
123
+ if (di < 0) di += i1;
124
+ else {
125
+ di += i2;
126
+ if (xAxis) y += incr;
127
+ else x += incr;
128
+ }
129
+ }
130
+ set(vm, client, "x", x);
131
+ set(vm, client, "y", y);
132
+
133
+ const cantBeHere = yield { object: client, selector: vm.selector("cantBeHere"), args: [] };
134
+ if (cantBeHere) {
135
+ set(vm, client, "x", origX);
136
+ set(vm, client, "y", origY);
137
+ di = origDi;
138
+ set(vm, client, "signal", get(vm, client, "signal") | SIGNAL_HIT_OBSTACLE);
139
+ }
140
+ set(vm, mover, "b-di", di);
141
+
142
+ const completed = get(vm, client, "x") === toX && get(vm, client, "y") === toY;
143
+ if (completed) yield { object: mover, selector: vm.selector("moveDone"), args: [] };
144
+ return completed ? 1 : 0;
145
+ },
146
+
147
+ DirLoop: (vm, [obj = 0, angle = 0]) => {
148
+ if (get(vm, obj, "signal") & SIGNAL_FIXED_LOOP) return;
149
+ const loops = graphics(vm).view(get(vm, obj, "view"))?.loops.length ?? 0;
150
+ let loop: number;
151
+ if (loops >= 4) {
152
+ if (angle > 315 || angle < 45) loop = 3; // north
153
+ else if (angle < 136) loop = 0; // east
154
+ else if (angle < 226) loop = 2; // south
155
+ else loop = 1; // west
156
+ } else loop = angle > 180 ? 1 : 0;
157
+ set(vm, obj, "loop", loop);
158
+ },
159
+
160
+ /**
161
+ * Actor-vs-actor collision: can `obj` stand where it is, given the other actors' base
162
+ * rects in `list`? (Walls are handled by pathfinding, not here.) Returns 1 if blocked.
163
+ */
164
+ CantBeHere: (vm, [obj = 0, list = 0]) => {
165
+ if (get(vm, obj, "signal") & COLLISION_EXEMPT) return 0;
166
+ const r = (o: Value) => ({ l: get(vm, o, "brLeft"), t: get(vm, o, "brTop"), r: get(vm, o, "brRight"), b: get(vm, o, "brBottom") });
167
+ const me = r(obj);
168
+ for (let n = vm.memory.list(list)?.first; n; n = n.next) {
169
+ const other = n.value;
170
+ if (other === obj || !vm.memory.object(other) || get(vm, other, "signal") & COLLISION_EXEMPT) continue;
171
+ const o = r(other);
172
+ // Strict overlap: touching isn't a collision (as in the original).
173
+ if (o.r > me.l && o.l < me.r && o.b > me.t && o.t < me.b) return 1;
174
+ }
175
+ return 0;
176
+ },
177
+
178
+ /** AvoidPath(x1, y1, x2, y2, polygonList, opt) → int16 array of points ending in 0x7777. */
179
+ AvoidPath: (vm, [x1 = 0, y1 = 0, x2 = 0, y2 = 0, list = 0]) => {
180
+ const start = { x: toSigned(x1), y: toSigned(y1) };
181
+ const end = { x: toSigned(x2), y: toSigned(y2) };
182
+ const path = list ? findPath(start, end, readPolygons(vm, list)) : [start, end];
183
+ const ref = vm.memory.newArray("int16", 0);
184
+ vm.memory.array(ref)!.data = [...path.flatMap((p) => [fromInt(Math.round(p.x)), fromInt(Math.round(p.y))]), POLY_END, POLY_END];
185
+ return ref;
186
+ },
187
+ InPolygon: (vm, [x = 0, y = 0, polygon = 0]) => {
188
+ const poly = readPolygon(vm, polygon);
189
+ return bool(!!poly && contains(poly, { x: toSigned(x), y: toSigned(y) }) !== "outside");
190
+ },
191
+ // TODO: merge a dynamic obstacle with overlapping barred polygons.
192
+ MergePoly: (_vm, [polygon = 0]) => polygon,
193
+ SetJump: () => 0,
194
+ };