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,218 @@
|
|
|
1
|
+
import type { Palette } from "./palette.ts";
|
|
2
|
+
import type { HunkPaletteFile, PicFile } from "./pic-file.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Turning RGBA art into a room picture, within an SCI2 game's palette layout (measured on all 59
|
|
6
|
+
* of its pics, the base palette 999 and the hero's views):
|
|
7
|
+
*
|
|
8
|
+
* 0-111 shared character colours: identical in every room and in the hero's views
|
|
9
|
+
* 112-235 124 colours each room chooses for itself
|
|
10
|
+
* 236-255 fixed interface/system colours (253, 254 are the shadow/light remaps, 255 is
|
|
11
|
+
* white and the pic cels' transparent colour)
|
|
12
|
+
*
|
|
13
|
+
* A new picture keeps 0-111 and 236-252 exactly as the base palette has them (it may use
|
|
14
|
+
* them in its art), picks its 124 own colours by k-means around those fixed ones, and never
|
|
15
|
+
* uses 253-255.
|
|
16
|
+
*/
|
|
17
|
+
export interface PicLayer {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
/** RGBA; alpha < 128 is transparent (only meaningful for foreground layers). */
|
|
21
|
+
rgba: Uint8Array;
|
|
22
|
+
/** Draw order against actors: -1000 for the background; else actors with y below it are behind. */
|
|
23
|
+
priority: number;
|
|
24
|
+
x?: number;
|
|
25
|
+
y?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface QuantizeOptions {
|
|
29
|
+
/** Floyd–Steinberg error diffusion: smoother gradients for painted art. Default true. */
|
|
30
|
+
dither?: boolean;
|
|
31
|
+
/** Lloyd iterations for the room's own colours. Default 8. */
|
|
32
|
+
iterations?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const FREE_FIRST = 112, FREE_LAST = 235;
|
|
36
|
+
const FIXED = [...Array.from({ length: 112 }, (_, i) => i), ...Array.from({ length: 17 }, (_, i) => 236 + i)]; // 0-111, 236-252
|
|
37
|
+
const SKIP = 255;
|
|
38
|
+
|
|
39
|
+
type Rgb = [number, number, number];
|
|
40
|
+
|
|
41
|
+
/** "Redmean": a cheap perceptual RGB distance. */
|
|
42
|
+
function distance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number {
|
|
43
|
+
const rm = (r1 + r2) / 2, dr = r1 - r2, dg = g1 - g2, db = b1 - b2;
|
|
44
|
+
return (2 + rm / 256) * dr * dr + 4 * dg * dg + (2 + (255 - rm) / 256) * db * db;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Deterministic pseudo-random numbers, so the same art always builds the same picture. */
|
|
48
|
+
function random(seed: number): () => number {
|
|
49
|
+
let s = seed >>> 0;
|
|
50
|
+
return () => ((s = (s * 1664525 + 1013904223) >>> 0) / 0x100000000);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildRoomPic(layers: PicLayer[], base: Palette, options: QuantizeOptions = {}): PicFile {
|
|
54
|
+
const { dither = true, iterations = 8 } = options;
|
|
55
|
+
if (!layers.length) throw new Error("A picture needs at least a background layer");
|
|
56
|
+
|
|
57
|
+
// Training pixels: every opaque pixel of every layer (sampled if there are many).
|
|
58
|
+
const all: Rgb[] = [];
|
|
59
|
+
for (const l of layers) for (let i = 0; i < l.width * l.height; i++) if (l.rgba[i * 4 + 3]! >= 128) all.push([l.rgba[i * 4]!, l.rgba[i * 4 + 1]!, l.rgba[i * 4 + 2]!]);
|
|
60
|
+
const rand = random(all.length);
|
|
61
|
+
const sample = all.length > 30_000 ? Array.from({ length: 30_000 }, () => all[Math.floor(rand() * all.length)]!) : all;
|
|
62
|
+
|
|
63
|
+
const fixed: Rgb[] = FIXED.map((i) => [base.rgb[i * 3]!, base.rgb[i * 3 + 1]!, base.rgb[i * 3 + 2]!]);
|
|
64
|
+
const nearest = (centers: Rgb[], r: number, g: number, b: number) => {
|
|
65
|
+
let best = 0, bestD = Infinity;
|
|
66
|
+
for (let c = 0; c < centers.length; c++) {
|
|
67
|
+
const k = centers[c]!;
|
|
68
|
+
const d = distance(r, g, b, k[0], k[1], k[2]);
|
|
69
|
+
if (d < bestD) (bestD = d), (best = c);
|
|
70
|
+
}
|
|
71
|
+
return { index: best, d: bestD };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// k-means++ seeding of the free colours, around the fixed ones.
|
|
75
|
+
const free: Rgb[] = [];
|
|
76
|
+
const dist = sample.map((p) => nearest(fixed, ...p).d);
|
|
77
|
+
const count = FREE_LAST - FREE_FIRST + 1;
|
|
78
|
+
while (free.length < count) {
|
|
79
|
+
const total = dist.reduce((a, b) => a + b, 0);
|
|
80
|
+
if (total === 0) {
|
|
81
|
+
free.push([0, 0, 0]); // the fixed colours already cover the art exactly
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
let t = rand() * total, pick = 0;
|
|
85
|
+
for (; pick < dist.length - 1 && (t -= dist[pick]!) > 0; pick++);
|
|
86
|
+
const c = sample[pick]!;
|
|
87
|
+
free.push([...c]);
|
|
88
|
+
for (let i = 0; i < sample.length; i++) dist[i] = Math.min(dist[i]!, distance(...sample[i]!, ...c));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Lloyd iterations: only the free colours move.
|
|
92
|
+
for (let it = 0; it < iterations; it++) {
|
|
93
|
+
const sums = free.map(() => [0, 0, 0, 0]);
|
|
94
|
+
const centers = [...fixed, ...free];
|
|
95
|
+
for (const p of sample) {
|
|
96
|
+
const { index } = nearest(centers, ...p);
|
|
97
|
+
if (index < fixed.length) continue;
|
|
98
|
+
const s = sums[index - fixed.length]!;
|
|
99
|
+
s[0]! += p[0]; s[1]! += p[1]; s[2]! += p[2]; s[3]!++;
|
|
100
|
+
}
|
|
101
|
+
sums.forEach((s, i) => {
|
|
102
|
+
if (s[3]) free[i] = [Math.round(s[0]! / s[3]), Math.round(s[1]! / s[3]), Math.round(s[2]! / s[3])];
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const centers = [...fixed, ...free];
|
|
107
|
+
const indexOf = [...FIXED, ...Array.from({ length: count }, (_, i) => FREE_FIRST + i)];
|
|
108
|
+
const map = (layer: PicLayer): Uint8Array => {
|
|
109
|
+
const { width: w, height: h, rgba } = layer;
|
|
110
|
+
const out = new Uint8Array(w * h).fill(SKIP);
|
|
111
|
+
const err = new Float32Array(w * h * 3);
|
|
112
|
+
for (let y = 0; y < h; y++) {
|
|
113
|
+
for (let x = 0; x < w; x++) {
|
|
114
|
+
const i = y * w + x;
|
|
115
|
+
if (rgba[i * 4 + 3]! < 128) continue;
|
|
116
|
+
const r = clamp(rgba[i * 4]! + err[i * 3]!), g = clamp(rgba[i * 4 + 1]! + err[i * 3 + 1]!), b = clamp(rgba[i * 4 + 2]! + err[i * 3 + 2]!);
|
|
117
|
+
const { index } = nearest(centers, r, g, b);
|
|
118
|
+
out[i] = indexOf[index]!;
|
|
119
|
+
if (!dither) continue;
|
|
120
|
+
const c = centers[index]!;
|
|
121
|
+
const e = [r - c[0], g - c[1], b - c[2]];
|
|
122
|
+
const spread = (dx: number, dy: number, f: number) => {
|
|
123
|
+
const xx = x + dx, yy = y + dy;
|
|
124
|
+
if (xx < 0 || xx >= w || yy >= h) return;
|
|
125
|
+
const j = yy * w + xx;
|
|
126
|
+
if (rgba[j * 4 + 3]! < 128) return;
|
|
127
|
+
for (let k = 0; k < 3; k++) err[j * 3 + k]! += e[k]! * f;
|
|
128
|
+
};
|
|
129
|
+
spread(1, 0, 7 / 16); spread(-1, 1, 3 / 16); spread(0, 1, 5 / 16); spread(1, 1, 1 / 16);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const cels = layers.map((l) => {
|
|
136
|
+
let pixels = map(l);
|
|
137
|
+
let [x, y, w, h] = [l.x ?? 0, l.y ?? 0, l.width, l.height];
|
|
138
|
+
if (l.priority > -1000) {
|
|
139
|
+
// Foreground pieces: crop to what's opaque.
|
|
140
|
+
let x0 = w, y0 = h, x1 = -1, y1 = -1;
|
|
141
|
+
for (let yy = 0; yy < h; yy++) for (let xx = 0; xx < w; xx++) if (pixels[yy * w + xx] !== SKIP) (x0 = Math.min(x0, xx)), (x1 = Math.max(x1, xx)), (y0 = Math.min(y0, yy)), (y1 = Math.max(y1, yy));
|
|
142
|
+
if (x1 < 0) throw new Error(`Layer at priority ${l.priority} is fully transparent`);
|
|
143
|
+
const cw = x1 - x0 + 1, ch = y1 - y0 + 1;
|
|
144
|
+
const cropped = new Uint8Array(cw * ch);
|
|
145
|
+
for (let yy = 0; yy < ch; yy++) cropped.set(pixels.subarray((y0 + yy) * w + x0, (y0 + yy) * w + x0 + cw), yy * cw);
|
|
146
|
+
[pixels, x, y, w, h] = [cropped, x + x0, y + y0, cw, ch];
|
|
147
|
+
}
|
|
148
|
+
return { width: w, height: h, displaceX: 0, displaceY: 0, skipColor: SKIP, pixels, unknown16: 2768, priority: l.priority, x, y };
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const rgb: [number, number, number][] = [];
|
|
152
|
+
for (let i = 0; i < 255; i++) {
|
|
153
|
+
const k: Rgb = i >= FREE_FIRST && i <= FREE_LAST ? free[i - FREE_FIRST]! : [base.rgb[i * 3]!, base.rgb[i * 3 + 1]!, base.rgb[i * 3 + 2]!];
|
|
154
|
+
rgb.push(k);
|
|
155
|
+
}
|
|
156
|
+
const palette: HunkPaletteFile = {
|
|
157
|
+
header: Uint8Array.from([14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]),
|
|
158
|
+
entryPrefix: Uint8Array.from([0, 255, 255, 255, 255, 255, 255, 255, 0, 0]),
|
|
159
|
+
first: 0,
|
|
160
|
+
used: rgb.map(() => 1),
|
|
161
|
+
rgb,
|
|
162
|
+
shared: false,
|
|
163
|
+
defaultUsed: 1,
|
|
164
|
+
};
|
|
165
|
+
return { resolution: [320, 200], cels, palette };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const clamp = (v: number) => (v < 0 ? 0 : v > 255 ? 255 : v);
|
|
169
|
+
|
|
170
|
+
/** The colours a view may use: shared character colours (+ fixed ones), or a room's too. */
|
|
171
|
+
export const SHARED_COLORS = FIXED;
|
|
172
|
+
export const ROOM_COLORS = [...FIXED, ...Array.from({ length: FREE_LAST - FREE_FIRST + 1 }, (_, i) => FREE_FIRST + i)];
|
|
173
|
+
export const SHADOW_COLOR = 254;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Maps RGBA art onto existing palette entries (no new colours): for sprites that must live
|
|
177
|
+
* with a room's or the shared palette. Alpha below 64 is transparent (`skip`), 64-191 is a
|
|
178
|
+
* shadow (the remap colour 254, which darkens what's behind), 192+ is opaque.
|
|
179
|
+
*/
|
|
180
|
+
export function mapToPalette(rgba: Uint8Array, width: number, height: number, palette: Palette, indices: number[], skip: number, dither = false): Uint8Array {
|
|
181
|
+
const centers = indices.map((i) => [palette.rgb[i * 3]!, palette.rgb[i * 3 + 1]!, palette.rgb[i * 3 + 2]!] as Rgb);
|
|
182
|
+
const out = new Uint8Array(width * height).fill(skip);
|
|
183
|
+
const err = new Float32Array(width * height * 3);
|
|
184
|
+
const cache = new Map<number, number>();
|
|
185
|
+
for (let y = 0; y < height; y++) {
|
|
186
|
+
for (let x = 0; x < width; x++) {
|
|
187
|
+
const i = y * width + x;
|
|
188
|
+
const a = rgba[i * 4 + 3]!;
|
|
189
|
+
if (a < 64) continue;
|
|
190
|
+
if (a < 192) {
|
|
191
|
+
out[i] = SHADOW_COLOR;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const r = clamp(rgba[i * 4]! + err[i * 3]!), g = clamp(rgba[i * 4 + 1]! + err[i * 3 + 1]!), b = clamp(rgba[i * 4 + 2]! + err[i * 3 + 2]!);
|
|
195
|
+
const key = (Math.round(r) << 16) | (Math.round(g) << 8) | Math.round(b);
|
|
196
|
+
let best = cache.get(key);
|
|
197
|
+
if (best === undefined) {
|
|
198
|
+
let bestD = Infinity;
|
|
199
|
+
best = 0;
|
|
200
|
+
centers.forEach((c, k) => {
|
|
201
|
+
const d = distance(r, g, b, c[0], c[1], c[2]);
|
|
202
|
+
if (d < bestD) (bestD = d), (best = k);
|
|
203
|
+
});
|
|
204
|
+
cache.set(key, best);
|
|
205
|
+
}
|
|
206
|
+
out[i] = indices[best]!;
|
|
207
|
+
if (!dither) continue;
|
|
208
|
+
const c = centers[best]!;
|
|
209
|
+
const e = [r - c[0], g - c[1], b - c[2]];
|
|
210
|
+
for (const [dx, dy, f] of [[1, 0, 7 / 16], [-1, 1, 3 / 16], [0, 1, 5 / 16], [1, 1, 1 / 16]] as const) {
|
|
211
|
+
const xx = x + dx, yy = y + dy;
|
|
212
|
+
if (xx < 0 || xx >= width || yy >= height) continue;
|
|
213
|
+
for (let k = 0; k < 3; k++) err[(yy * width + xx) * 3 + k]! += e[k]! * f;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Cel } from "./cel.ts";
|
|
2
|
+
import { type HunkPaletteFile } from "./pic-file.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A view resource with everything needed to write it back byte for byte. Measured on all
|
|
5
|
+
* 500 views of an SCI2 game (every one has this layout; all 9,628 cels are RLE-compressed):
|
|
6
|
+
*
|
|
7
|
+
* header (18): +0 u16 16 +2 u8 loop count +3 u8 0 +4 u8 `flags` (1) +5 u8 0
|
|
8
|
+
* +6 u8 total cels +7 u8 0 +8 u32 palette offset +12 u8 16 +13 u8 36
|
|
9
|
+
* loop headers (16): +0 i8 linked loop (-1 = own cels) +1 u8 mirror +2 u8 cel count
|
|
10
|
+
* +3 ff ff ff ff 03 00 00 00 00 +12 u32 first cel header
|
|
11
|
+
* (a linked loop's points at where the next cel headers begin)
|
|
12
|
+
* cel headers (36), loop by loop: +0 u16 w +2 u16 h +4 i16 dx +6 i16 dy +8 u8 skip
|
|
13
|
+
* +9 u8 0x8a +12 u32 control+literal bytes +16 u32 control bytes
|
|
14
|
+
* +24 control offset +28 literal offset +32 row table offset
|
|
15
|
+
* 00 03 u32 size | hunk palette
|
|
16
|
+
* 00 04 u32 size | every cel's control stream, then every cel's literal stream
|
|
17
|
+
* 00 05 u32 size | every cel's row table (u32 control offsets, then u32 literal offsets)
|
|
18
|
+
*/
|
|
19
|
+
export interface ViewFile {
|
|
20
|
+
flags: number;
|
|
21
|
+
loops: ViewLoop[];
|
|
22
|
+
palette: HunkPaletteFile | undefined;
|
|
23
|
+
/** The two chunk-size words (498 views: control+literal bytes, 0; 2 views: control, literal). */
|
|
24
|
+
chunkSizes?: [number, number];
|
|
25
|
+
}
|
|
26
|
+
export interface ViewLoop {
|
|
27
|
+
/** -1: this loop has its own cels; otherwise it reuses loop `link`'s (mirrored if `mirror`). */
|
|
28
|
+
link: number;
|
|
29
|
+
mirror: boolean;
|
|
30
|
+
cels: ViewCel[];
|
|
31
|
+
}
|
|
32
|
+
export interface ViewCel extends Cel {
|
|
33
|
+
/**
|
|
34
|
+
* The cel's compressed streams as read, reused while `pixels` still equals `original`.
|
|
35
|
+
* Sierra's encoder follows heuristics we don't reproduce exactly,
|
|
36
|
+
* so keeping them is what makes untouched views round-trip byte for byte.
|
|
37
|
+
*/
|
|
38
|
+
encoded?: RleCel & {
|
|
39
|
+
original: Uint8Array;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface RleCel {
|
|
43
|
+
control: number[] | Uint8Array;
|
|
44
|
+
literal: number[] | Uint8Array;
|
|
45
|
+
rowControl: number[];
|
|
46
|
+
rowLiteral: number[];
|
|
47
|
+
}
|
|
48
|
+
export declare function parseViewFile(data: Uint8Array): ViewFile;
|
|
49
|
+
/**
|
|
50
|
+
* SCI32 cel RLE, one row at a time: transparent pixels as transparent runs (11nnnnnn), equal
|
|
51
|
+
* pixels as colour runs (10nnnnnn + the colour in the literal stream), the rest as literals
|
|
52
|
+
* (0nnnnnnn + that many bytes); runs hold up to 63 pixels, literal groups up to 127.
|
|
53
|
+
*
|
|
54
|
+
* Each row is split to use the fewest bytes: a transparent run costs 1, a colour run 2, a
|
|
55
|
+
* literal group 1 + its length (and may include a lone transparent pixel when that's
|
|
56
|
+
* cheaper, as Sierra's encoder also does). The result is never larger than Sierra's.
|
|
57
|
+
*/
|
|
58
|
+
export declare function encodeCelRle(cel: Cel): RleCel & {
|
|
59
|
+
control: number[];
|
|
60
|
+
literal: number[];
|
|
61
|
+
};
|
|
62
|
+
export declare function writeView(view: ViewFile): Uint8Array;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { decodeCel } from "./cel.js";
|
|
2
|
+
import { parseHunkPaletteFile, writeHunkPalette } from "./pic-file.js";
|
|
3
|
+
const HEADER = 18, LOOP = 16, CEL = 36;
|
|
4
|
+
const LOOP_FILLER = [0xff, 0xff, 0xff, 0xff, 0x03, 0, 0, 0, 0];
|
|
5
|
+
export function parseViewFile(data) {
|
|
6
|
+
const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
7
|
+
const loopCount = data[2];
|
|
8
|
+
const palette = v.getUint32(8, true);
|
|
9
|
+
const loops = [];
|
|
10
|
+
for (let i = 0; i < loopCount; i++) {
|
|
11
|
+
const lh = HEADER + LOOP * i;
|
|
12
|
+
const link = v.getInt8(lh);
|
|
13
|
+
const first = v.getUint32(lh + 12, true);
|
|
14
|
+
loops.push({
|
|
15
|
+
link,
|
|
16
|
+
mirror: data[lh + 1] === 1,
|
|
17
|
+
cels: link === -1 ? Array.from({ length: data[lh + 2] }, (_, c) => readCel(data, first + CEL * c)) : [],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
let chunkSizes;
|
|
21
|
+
const firstCel = loops.findIndex((l) => l.link === -1 && l.cels.length);
|
|
22
|
+
if (firstCel >= 0) {
|
|
23
|
+
const h = v.getUint32(HEADER + LOOP * firstCel + 12, true);
|
|
24
|
+
const ctrl = v.getUint32(h + 24, true), rows = v.getUint32(h + 32, true);
|
|
25
|
+
const litEnd = rows - 6;
|
|
26
|
+
chunkSizes = [v.getUint32(ctrl - 4, true), v.getUint32(litEnd + 2, true)];
|
|
27
|
+
}
|
|
28
|
+
return { flags: data[4], loops, palette: palette ? parseHunkPaletteFile(data, palette) : undefined, chunkSizes };
|
|
29
|
+
}
|
|
30
|
+
function readCel(data, h) {
|
|
31
|
+
const cel = decodeCel(data, h);
|
|
32
|
+
const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
33
|
+
const ctrlSize = v.getUint32(h + 16, true), total = v.getUint32(h + 12, true);
|
|
34
|
+
const ctrl = v.getUint32(h + 24, true), lit = v.getUint32(h + 28, true), rows = v.getUint32(h + 32, true);
|
|
35
|
+
cel.encoded = {
|
|
36
|
+
control: data.slice(ctrl, ctrl + ctrlSize),
|
|
37
|
+
literal: data.slice(lit, lit + total - ctrlSize),
|
|
38
|
+
rowControl: Array.from({ length: cel.height }, (_, y) => v.getUint32(rows + y * 4, true)),
|
|
39
|
+
rowLiteral: Array.from({ length: cel.height }, (_, y) => v.getUint32(rows + (cel.height + y) * 4, true)),
|
|
40
|
+
original: cel.pixels.slice(),
|
|
41
|
+
};
|
|
42
|
+
return cel;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* SCI32 cel RLE, one row at a time: transparent pixels as transparent runs (11nnnnnn), equal
|
|
46
|
+
* pixels as colour runs (10nnnnnn + the colour in the literal stream), the rest as literals
|
|
47
|
+
* (0nnnnnnn + that many bytes); runs hold up to 63 pixels, literal groups up to 127.
|
|
48
|
+
*
|
|
49
|
+
* Each row is split to use the fewest bytes: a transparent run costs 1, a colour run 2, a
|
|
50
|
+
* literal group 1 + its length (and may include a lone transparent pixel when that's
|
|
51
|
+
* cheaper, as Sierra's encoder also does). The result is never larger than Sierra's.
|
|
52
|
+
*/
|
|
53
|
+
export function encodeCelRle(cel) {
|
|
54
|
+
const control = [], literal = [], rowControl = [], rowLiteral = [];
|
|
55
|
+
const { width: w, height: h, pixels, skipColor } = cel;
|
|
56
|
+
for (let y = 0; y < h; y++) {
|
|
57
|
+
rowControl.push(control.length);
|
|
58
|
+
rowLiteral.push(literal.length);
|
|
59
|
+
const row = pixels.subarray(y * w, (y + 1) * w);
|
|
60
|
+
let x = 0;
|
|
61
|
+
for (const [kind, n] of cheapestSplit(row, skipColor)) {
|
|
62
|
+
if (kind === "skip")
|
|
63
|
+
control.push(0xc0 | n);
|
|
64
|
+
else if (kind === "run") {
|
|
65
|
+
control.push(0x80 | n);
|
|
66
|
+
literal.push(row[x]);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
control.push(n);
|
|
70
|
+
for (let k = 0; k < n; k++)
|
|
71
|
+
literal.push(row[x + k]);
|
|
72
|
+
}
|
|
73
|
+
x += n;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { control, literal, rowControl, rowLiteral };
|
|
77
|
+
}
|
|
78
|
+
/** Fewest bytes for a row: dynamic programming from the end. */
|
|
79
|
+
function cheapestSplit(row, skipColor) {
|
|
80
|
+
const n = row.length;
|
|
81
|
+
const cost = new Array(n + 1).fill(Infinity);
|
|
82
|
+
const pick = new Array(n);
|
|
83
|
+
cost[n] = 0;
|
|
84
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
85
|
+
let same = 1;
|
|
86
|
+
while (i + same < n && row[i + same] === row[i] && same < 63)
|
|
87
|
+
same++;
|
|
88
|
+
const transparent = row[i] === skipColor;
|
|
89
|
+
for (let k = same; k >= (transparent ? 1 : 2); k--) {
|
|
90
|
+
const c = (transparent ? 1 : 2) + cost[i + k];
|
|
91
|
+
if (c < cost[i])
|
|
92
|
+
(cost[i] = c), (pick[i] = [transparent ? "skip" : "run", k]);
|
|
93
|
+
}
|
|
94
|
+
for (let k = 1; k <= Math.min(127, n - i); k++) {
|
|
95
|
+
const c = 1 + k + cost[i + k];
|
|
96
|
+
if (c < cost[i])
|
|
97
|
+
(cost[i] = c), (pick[i] = ["lit", k]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const out = [];
|
|
101
|
+
for (let i = 0; i < n; i += pick[i][1])
|
|
102
|
+
out.push(pick[i]);
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
/** The cel's streams: as read if its pixels are untouched, else freshly encoded. */
|
|
106
|
+
function streamsOf(cel) {
|
|
107
|
+
const e = cel.encoded;
|
|
108
|
+
if (e && e.original.length === cel.pixels.length && e.original.every((p, i) => p === cel.pixels[i]))
|
|
109
|
+
return e;
|
|
110
|
+
return encodeCelRle(cel);
|
|
111
|
+
}
|
|
112
|
+
export function writeView(view) {
|
|
113
|
+
const own = view.loops.filter((l) => l.link === -1);
|
|
114
|
+
const cels = own.flatMap((l) => l.cels);
|
|
115
|
+
const encoded = cels.map(streamsOf);
|
|
116
|
+
const palette = view.palette ? writeHunkPalette(view.palette) : undefined;
|
|
117
|
+
const headersEnd = HEADER + LOOP * view.loops.length + CEL * cels.length;
|
|
118
|
+
const paletteAt = palette ? headersEnd + 6 : 0;
|
|
119
|
+
const ctrlChunk = (palette ? paletteAt + palette.length : headersEnd) + 6;
|
|
120
|
+
const ctrlSize = encoded.reduce((n, e) => n + e.control.length, 0);
|
|
121
|
+
const litSize = encoded.reduce((n, e) => n + e.literal.length, 0);
|
|
122
|
+
const litAt = ctrlChunk + ctrlSize;
|
|
123
|
+
const rowsAt = litAt + litSize + 6;
|
|
124
|
+
const rowsSize = cels.reduce((n, c) => n + c.height * 8, 0);
|
|
125
|
+
const out = new Uint8Array(rowsAt + rowsSize);
|
|
126
|
+
const v = new DataView(out.buffer);
|
|
127
|
+
v.setUint16(0, HEADER - 2, true);
|
|
128
|
+
out[2] = view.loops.length;
|
|
129
|
+
out[4] = view.flags;
|
|
130
|
+
out[6] = cels.length;
|
|
131
|
+
v.setUint32(8, paletteAt, true);
|
|
132
|
+
out[12] = LOOP;
|
|
133
|
+
out[13] = CEL;
|
|
134
|
+
// Loop headers; cel headers follow in loop order.
|
|
135
|
+
let celHeader = HEADER + LOOP * view.loops.length;
|
|
136
|
+
let celIndex = 0;
|
|
137
|
+
let ctrl = ctrlChunk, lit = litAt, rows = rowsAt;
|
|
138
|
+
view.loops.forEach((loop, i) => {
|
|
139
|
+
const lh = HEADER + LOOP * i;
|
|
140
|
+
v.setInt8(lh, loop.link);
|
|
141
|
+
out[lh + 1] = loop.mirror ? 1 : 0;
|
|
142
|
+
out[lh + 2] = loop.link === -1 ? loop.cels.length : 0;
|
|
143
|
+
out.set(LOOP_FILLER, lh + 3);
|
|
144
|
+
v.setUint32(lh + 12, celHeader, true);
|
|
145
|
+
if (loop.link !== -1)
|
|
146
|
+
return;
|
|
147
|
+
for (const cel of loop.cels) {
|
|
148
|
+
const e = encoded[celIndex++];
|
|
149
|
+
const h = celHeader;
|
|
150
|
+
v.setUint16(h, cel.width, true);
|
|
151
|
+
v.setUint16(h + 2, cel.height, true);
|
|
152
|
+
v.setInt16(h + 4, cel.displaceX, true);
|
|
153
|
+
v.setInt16(h + 6, cel.displaceY, true);
|
|
154
|
+
out[h + 8] = cel.skipColor;
|
|
155
|
+
out[h + 9] = 0x8a;
|
|
156
|
+
v.setUint32(h + 12, e.control.length + e.literal.length, true);
|
|
157
|
+
v.setUint32(h + 16, e.control.length, true);
|
|
158
|
+
v.setUint32(h + 24, ctrl, true);
|
|
159
|
+
v.setUint32(h + 28, lit, true);
|
|
160
|
+
v.setUint32(h + 32, rows, true);
|
|
161
|
+
out.set(e.control, ctrl);
|
|
162
|
+
out.set(e.literal, lit);
|
|
163
|
+
e.rowControl.forEach((o, y) => v.setUint32(rows + y * 4, o, true));
|
|
164
|
+
e.rowLiteral.forEach((o, y) => v.setUint32(rows + (cel.height + y) * 4, o, true));
|
|
165
|
+
ctrl += e.control.length;
|
|
166
|
+
lit += e.literal.length;
|
|
167
|
+
rows += cel.height * 8;
|
|
168
|
+
celHeader += CEL;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
const [size4, size5] = view.chunkSizes ?? [ctrlSize + litSize, 0];
|
|
172
|
+
if (palette) {
|
|
173
|
+
out.set([0, 3], headersEnd);
|
|
174
|
+
v.setUint32(headersEnd + 2, palette.length, true);
|
|
175
|
+
out.set(palette, paletteAt);
|
|
176
|
+
}
|
|
177
|
+
out.set([0, 4], ctrlChunk - 6);
|
|
178
|
+
v.setUint32(ctrlChunk - 4, size4, true);
|
|
179
|
+
out.set([0, 5], rowsAt - 6);
|
|
180
|
+
v.setUint32(rowsAt - 4, size5, true);
|
|
181
|
+
return out;
|
|
182
|
+
}
|