sci2-ts 0.0.0-stage → 0.2.1
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 +72 -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,155 @@
|
|
|
1
|
+
import type { Palette } from "./palette.ts";
|
|
2
|
+
import type { PicCel } from "./pic.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A pic resource with everything needed to write it back byte for byte. Measured on all 59
|
|
6
|
+
* pics of an SCI2 game (every one has this exact layout; all cels are uncompressed):
|
|
7
|
+
*
|
|
8
|
+
* +0 u16 header size (14) +2 u8 cel count +3 u8 0 +4 u16 cel header size (42)
|
|
9
|
+
* +6 u32 palette offset +10 u16/u16 resolution (320,200, or 0,0 meaning the default)
|
|
10
|
+
* cel headers, 42 bytes each:
|
|
11
|
+
* +0 u16 width +2 u16 height +4 i16 displaceX +6 i16 displaceY +8 u8 skip colour
|
|
12
|
+
* +9 u8 compression (0 = raw) +10 u16 0 +12 u32 pixel bytes (w*h)
|
|
13
|
+
* +16 u32 `unknown16` (2768 in most pics, 6 in a few; the engine ignores it)
|
|
14
|
+
* +20 u32 0 +24 u32 pixel offset +28 u32 0 +32 u32 0
|
|
15
|
+
* +36 i16 priority +38 i16 x +40 i16 y
|
|
16
|
+
* 00 03 u32 palette size | hunk palette
|
|
17
|
+
* 00 04 u32 pixel bytes | each cel's pixels, in cel order
|
|
18
|
+
*/
|
|
19
|
+
export interface PicFile {
|
|
20
|
+
resolution: [number, number];
|
|
21
|
+
cels: (PicCel & { unknown16: number })[];
|
|
22
|
+
palette: HunkPaletteFile;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A hunk palette with its exact header bytes. Pics use one entry: `first` and
|
|
27
|
+
* `count` colours, each with its own "used" flag (4 bytes per colour) unless `shared`.
|
|
28
|
+
*/
|
|
29
|
+
export interface HunkPaletteFile {
|
|
30
|
+
/** The 13-byte hunk header (byte 0 = 14, byte 10 = entry count = 1). */
|
|
31
|
+
header: Uint8Array;
|
|
32
|
+
/** The first 10 bytes of the entry header (editor data; varies, never read). */
|
|
33
|
+
entryPrefix: Uint8Array;
|
|
34
|
+
first: number;
|
|
35
|
+
used: number[];
|
|
36
|
+
rgb: [number, number, number][];
|
|
37
|
+
/** Shared "used" flag: every colour is 3 bytes and takes `defaultUsed`. */
|
|
38
|
+
shared: boolean;
|
|
39
|
+
defaultUsed: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const PIC_HEADER = 14;
|
|
43
|
+
const CEL_HEADER = 42;
|
|
44
|
+
|
|
45
|
+
export function parseHunkPaletteFile(data: Uint8Array, offset: number): HunkPaletteFile {
|
|
46
|
+
const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
47
|
+
if (data[offset + 10] !== 1) throw new Error(`Hunk palette with ${data[offset + 10]} entries: only 1 is supported`);
|
|
48
|
+
const entry = offset + 13 + 2;
|
|
49
|
+
const first = data[entry + 10]!;
|
|
50
|
+
const count = v.getUint16(entry + 14, true);
|
|
51
|
+
const defaultUsed = data[entry + 16]!;
|
|
52
|
+
const shared = data[entry + 17]! !== 0;
|
|
53
|
+
const used: number[] = [], rgb: [number, number, number][] = [];
|
|
54
|
+
let p = entry + 22;
|
|
55
|
+
for (let i = 0; i < count; i++) {
|
|
56
|
+
used.push(shared ? defaultUsed : data[p++]!);
|
|
57
|
+
rgb.push([data[p]!, data[p + 1]!, data[p + 2]!]);
|
|
58
|
+
p += 3;
|
|
59
|
+
}
|
|
60
|
+
return { header: data.slice(offset, offset + 13), entryPrefix: data.slice(entry, entry + 10), first, used, rgb, shared, defaultUsed };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function writeHunkPalette(p: HunkPaletteFile): Uint8Array {
|
|
64
|
+
const colors = p.rgb.length * (p.shared ? 3 : 4);
|
|
65
|
+
const out = new Uint8Array(13 + 2 + 22 + colors);
|
|
66
|
+
const v = new DataView(out.buffer);
|
|
67
|
+
out.set(p.header, 0);
|
|
68
|
+
out[10] = 1;
|
|
69
|
+
v.setUint16(13, 22 + colors, true);
|
|
70
|
+
const entry = 15;
|
|
71
|
+
out.set(p.entryPrefix, entry);
|
|
72
|
+
out[entry + 10] = p.first;
|
|
73
|
+
v.setUint16(entry + 14, p.rgb.length, true);
|
|
74
|
+
out[entry + 16] = p.defaultUsed;
|
|
75
|
+
out[entry + 17] = p.shared ? 1 : 0;
|
|
76
|
+
let o = entry + 22;
|
|
77
|
+
p.rgb.forEach((c, i) => {
|
|
78
|
+
if (!p.shared) out[o++] = p.used[i]!;
|
|
79
|
+
out.set(c, o);
|
|
80
|
+
o += 3;
|
|
81
|
+
});
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function parsePicFile(data: Uint8Array): PicFile {
|
|
86
|
+
const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
87
|
+
const count = data[2]!;
|
|
88
|
+
const cels = Array.from({ length: count }, (_, i) => {
|
|
89
|
+
const h = PIC_HEADER + CEL_HEADER * i;
|
|
90
|
+
if (data[h + 9] !== 0) throw new Error(`Compressed pic cels aren't supported (cel ${i})`);
|
|
91
|
+
const width = v.getUint16(h, true), height = v.getUint16(h + 2, true);
|
|
92
|
+
const at = v.getUint32(h + 24, true);
|
|
93
|
+
return {
|
|
94
|
+
width, height,
|
|
95
|
+
displaceX: v.getInt16(h + 4, true), displaceY: v.getInt16(h + 6, true),
|
|
96
|
+
skipColor: data[h + 8]!,
|
|
97
|
+
pixels: data.slice(at, at + width * height),
|
|
98
|
+
unknown16: v.getUint32(h + 16, true),
|
|
99
|
+
priority: v.getInt16(h + 36, true), x: v.getInt16(h + 38, true), y: v.getInt16(h + 40, true),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
return { resolution: [v.getUint16(10, true), v.getUint16(12, true)], cels, palette: parseHunkPaletteFile(data, v.getUint32(6, true)) };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function writePic(pic: PicFile): Uint8Array {
|
|
106
|
+
const palette = writeHunkPalette(pic.palette);
|
|
107
|
+
const headers = PIC_HEADER + CEL_HEADER * pic.cels.length;
|
|
108
|
+
const paletteAt = headers + 6;
|
|
109
|
+
const pixelsAt = paletteAt + palette.length + 6;
|
|
110
|
+
const pixelBytes = pic.cels.reduce((n, c) => n + c.width * c.height, 0);
|
|
111
|
+
const out = new Uint8Array(pixelsAt + pixelBytes);
|
|
112
|
+
const v = new DataView(out.buffer);
|
|
113
|
+
v.setUint16(0, PIC_HEADER, true);
|
|
114
|
+
out[2] = pic.cels.length;
|
|
115
|
+
v.setUint16(4, CEL_HEADER, true);
|
|
116
|
+
v.setUint32(6, paletteAt, true);
|
|
117
|
+
v.setUint16(10, pic.resolution[0], true);
|
|
118
|
+
v.setUint16(12, pic.resolution[1], true);
|
|
119
|
+
let at = pixelsAt;
|
|
120
|
+
pic.cels.forEach((c, i) => {
|
|
121
|
+
const h = PIC_HEADER + CEL_HEADER * i;
|
|
122
|
+
if (c.pixels.length !== c.width * c.height) throw new Error(`cel ${i}: ${c.pixels.length} pixels for ${c.width}x${c.height}`);
|
|
123
|
+
v.setUint16(h, c.width, true);
|
|
124
|
+
v.setUint16(h + 2, c.height, true);
|
|
125
|
+
v.setInt16(h + 4, c.displaceX, true);
|
|
126
|
+
v.setInt16(h + 6, c.displaceY, true);
|
|
127
|
+
out[h + 8] = c.skipColor;
|
|
128
|
+
v.setUint32(h + 12, c.width * c.height, true);
|
|
129
|
+
v.setUint32(h + 16, c.unknown16, true);
|
|
130
|
+
v.setUint32(h + 24, at, true);
|
|
131
|
+
v.setInt16(h + 36, c.priority, true);
|
|
132
|
+
v.setInt16(h + 38, c.x, true);
|
|
133
|
+
v.setInt16(h + 40, c.y, true);
|
|
134
|
+
out.set(c.pixels, at);
|
|
135
|
+
at += c.pixels.length;
|
|
136
|
+
});
|
|
137
|
+
out.set([0, 3], headers);
|
|
138
|
+
v.setUint32(headers + 2, palette.length, true);
|
|
139
|
+
out.set(palette, paletteAt);
|
|
140
|
+
out.set([0, 4], paletteAt + palette.length);
|
|
141
|
+
v.setUint32(paletteAt + palette.length + 2, pixelBytes, true);
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The palette a pic installs, as the engine sees it. */
|
|
146
|
+
export function hunkToPalette(p: HunkPaletteFile): Palette {
|
|
147
|
+
const pal: Palette = { rgb: new Uint8Array(768), used: new Uint8Array(256) };
|
|
148
|
+
p.rgb.forEach((c, i) => {
|
|
149
|
+
const idx = p.first + i;
|
|
150
|
+
if (idx > 255) return;
|
|
151
|
+
pal.used[idx] = p.used[i]!;
|
|
152
|
+
pal.rgb.set(c, idx * 3);
|
|
153
|
+
});
|
|
154
|
+
return pal;
|
|
155
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Cel } from "./cel.ts";
|
|
2
|
+
import { type Palette } from "./palette.ts";
|
|
3
|
+
export interface PicCel extends Cel {
|
|
4
|
+
/** Draw order against other pic layers and actors. */
|
|
5
|
+
priority: number;
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
}
|
|
9
|
+
/** A pic: a room background made of one or more positioned cel layers. */
|
|
10
|
+
export interface Pic {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
cels: PicCel[];
|
|
14
|
+
palette: Palette | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* SCI32 pic layout:
|
|
18
|
+
*
|
|
19
|
+
* +0 u16 header size (first cel header starts here)
|
|
20
|
+
* +2 u8 cel count
|
|
21
|
+
* +4 u16 cel header size
|
|
22
|
+
* +6 u32 embedded palette offset
|
|
23
|
+
* +10 u16/u16 resolution: (w, h) if the second is non-zero, else a code (0 = 320x200, 1 = 640x480, 2 = 640x400)
|
|
24
|
+
*
|
|
25
|
+
* Cel headers are the common layout plus +36 i16 priority, +38 i16 x, +40 i16 y.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parsePic(data: Uint8Array): Pic;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { decodeCel } from "./cel.js";
|
|
2
|
+
import { parseHunkPalette } from "./palette.js";
|
|
3
|
+
/**
|
|
4
|
+
* SCI32 pic layout:
|
|
5
|
+
*
|
|
6
|
+
* +0 u16 header size (first cel header starts here)
|
|
7
|
+
* +2 u8 cel count
|
|
8
|
+
* +4 u16 cel header size
|
|
9
|
+
* +6 u32 embedded palette offset
|
|
10
|
+
* +10 u16/u16 resolution: (w, h) if the second is non-zero, else a code (0 = 320x200, 1 = 640x480, 2 = 640x400)
|
|
11
|
+
*
|
|
12
|
+
* Cel headers are the common layout plus +36 i16 priority, +38 i16 x, +40 i16 y.
|
|
13
|
+
*/
|
|
14
|
+
export function parsePic(data) {
|
|
15
|
+
const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
16
|
+
const headerSize = v.getUint16(0, true);
|
|
17
|
+
const celCount = data[2];
|
|
18
|
+
const celHeaderSize = v.getUint16(4, true);
|
|
19
|
+
const paletteOffset = v.getUint32(6, true);
|
|
20
|
+
const res1 = v.getUint16(10, true);
|
|
21
|
+
const res2 = v.getUint16(12, true);
|
|
22
|
+
const presets = [[320, 200], [640, 480], [640, 400]];
|
|
23
|
+
const [width, height] = res2 ? [res1, res2] : (presets[res1] ?? [320, 200]);
|
|
24
|
+
const cels = Array.from({ length: celCount }, (_, i) => {
|
|
25
|
+
const h = headerSize + celHeaderSize * i;
|
|
26
|
+
return {
|
|
27
|
+
...decodeCel(data, h),
|
|
28
|
+
priority: v.getInt16(h + 36, true),
|
|
29
|
+
x: v.getInt16(h + 38, true),
|
|
30
|
+
y: v.getInt16(h + 40, true),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
return { width, height, cels, palette: paletteOffset ? parseHunkPalette(data, paletteOffset) : undefined };
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { decodeCel, type Cel } from "./cel.ts";
|
|
2
|
+
import { parseHunkPalette, type Palette } from "./palette.ts";
|
|
3
|
+
|
|
4
|
+
export interface PicCel extends Cel {
|
|
5
|
+
/** Draw order against other pic layers and actors. */
|
|
6
|
+
priority: number;
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** A pic: a room background made of one or more positioned cel layers. */
|
|
12
|
+
export interface Pic {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
cels: PicCel[];
|
|
16
|
+
palette: Palette | undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SCI32 pic layout:
|
|
21
|
+
*
|
|
22
|
+
* +0 u16 header size (first cel header starts here)
|
|
23
|
+
* +2 u8 cel count
|
|
24
|
+
* +4 u16 cel header size
|
|
25
|
+
* +6 u32 embedded palette offset
|
|
26
|
+
* +10 u16/u16 resolution: (w, h) if the second is non-zero, else a code (0 = 320x200, 1 = 640x480, 2 = 640x400)
|
|
27
|
+
*
|
|
28
|
+
* Cel headers are the common layout plus +36 i16 priority, +38 i16 x, +40 i16 y.
|
|
29
|
+
*/
|
|
30
|
+
export function parsePic(data: Uint8Array): Pic {
|
|
31
|
+
const v = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
32
|
+
const headerSize = v.getUint16(0, true);
|
|
33
|
+
const celCount = data[2]!;
|
|
34
|
+
const celHeaderSize = v.getUint16(4, true);
|
|
35
|
+
const paletteOffset = v.getUint32(6, true);
|
|
36
|
+
|
|
37
|
+
const res1 = v.getUint16(10, true);
|
|
38
|
+
const res2 = v.getUint16(12, true);
|
|
39
|
+
const presets: [number, number][] = [[320, 200], [640, 480], [640, 400]];
|
|
40
|
+
const [width, height] = res2 ? [res1, res2] : (presets[res1] ?? [320, 200]);
|
|
41
|
+
|
|
42
|
+
const cels = Array.from({ length: celCount }, (_, i): PicCel => {
|
|
43
|
+
const h = headerSize + celHeaderSize * i;
|
|
44
|
+
return {
|
|
45
|
+
...decodeCel(data, h),
|
|
46
|
+
priority: v.getInt16(h + 36, true),
|
|
47
|
+
x: v.getInt16(h + 38, true),
|
|
48
|
+
y: v.getInt16(h + 40, true),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return { width, height, cels, palette: paletteOffset ? parseHunkPalette(data, paletteOffset) : undefined };
|
|
53
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Palette } from "./palette.ts";
|
|
2
|
+
import type { PicFile } from "./pic-file.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Turning RGBA art into a room picture, within an SCI2 game's palette layout (measured on all 59
|
|
5
|
+
* of its pics, the base palette 999 and the hero's views):
|
|
6
|
+
*
|
|
7
|
+
* 0-111 shared character colours: identical in every room and in the hero's views
|
|
8
|
+
* 112-235 124 colours each room chooses for itself
|
|
9
|
+
* 236-255 fixed interface/system colours (253, 254 are the shadow/light remaps, 255 is
|
|
10
|
+
* white and the pic cels' transparent colour)
|
|
11
|
+
*
|
|
12
|
+
* A new picture keeps 0-111 and 236-252 exactly as the base palette has them (it may use
|
|
13
|
+
* them in its art), picks its 124 own colours by k-means around those fixed ones, and never
|
|
14
|
+
* uses 253-255.
|
|
15
|
+
*/
|
|
16
|
+
export interface PicLayer {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
/** RGBA; alpha < 128 is transparent (only meaningful for foreground layers). */
|
|
20
|
+
rgba: Uint8Array;
|
|
21
|
+
/** Draw order against actors: -1000 for the background; else actors with y below it are behind. */
|
|
22
|
+
priority: number;
|
|
23
|
+
x?: number;
|
|
24
|
+
y?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface QuantizeOptions {
|
|
27
|
+
/** Floyd–Steinberg error diffusion: smoother gradients for painted art. Default true. */
|
|
28
|
+
dither?: boolean;
|
|
29
|
+
/** Lloyd iterations for the room's own colours. Default 8. */
|
|
30
|
+
iterations?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function buildRoomPic(layers: PicLayer[], base: Palette, options?: QuantizeOptions): PicFile;
|
|
33
|
+
/** The colours a view may use: shared character colours (+ fixed ones), or a room's too. */
|
|
34
|
+
export declare const SHARED_COLORS: number[];
|
|
35
|
+
export declare const ROOM_COLORS: number[];
|
|
36
|
+
export declare const SHADOW_COLOR = 254;
|
|
37
|
+
/**
|
|
38
|
+
* Maps RGBA art onto existing palette entries (no new colours): for sprites that must live
|
|
39
|
+
* with a room's or the shared palette. Alpha below 64 is transparent (`skip`), 64-191 is a
|
|
40
|
+
* shadow (the remap colour 254, which darkens what's behind), 192+ is opaque.
|
|
41
|
+
*/
|
|
42
|
+
export declare function mapToPalette(rgba: Uint8Array, width: number, height: number, palette: Palette, indices: number[], skip: number, dither?: boolean): Uint8Array;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const FREE_FIRST = 112, FREE_LAST = 235;
|
|
2
|
+
const FIXED = [...Array.from({ length: 112 }, (_, i) => i), ...Array.from({ length: 17 }, (_, i) => 236 + i)]; // 0-111, 236-252
|
|
3
|
+
const SKIP = 255;
|
|
4
|
+
/** "Redmean": a cheap perceptual RGB distance. */
|
|
5
|
+
function distance(r1, g1, b1, r2, g2, b2) {
|
|
6
|
+
const rm = (r1 + r2) / 2, dr = r1 - r2, dg = g1 - g2, db = b1 - b2;
|
|
7
|
+
return (2 + rm / 256) * dr * dr + 4 * dg * dg + (2 + (255 - rm) / 256) * db * db;
|
|
8
|
+
}
|
|
9
|
+
/** Deterministic pseudo-random numbers, so the same art always builds the same picture. */
|
|
10
|
+
function random(seed) {
|
|
11
|
+
let s = seed >>> 0;
|
|
12
|
+
return () => ((s = (s * 1664525 + 1013904223) >>> 0) / 0x100000000);
|
|
13
|
+
}
|
|
14
|
+
export function buildRoomPic(layers, base, options = {}) {
|
|
15
|
+
const { dither = true, iterations = 8 } = options;
|
|
16
|
+
if (!layers.length)
|
|
17
|
+
throw new Error("A picture needs at least a background layer");
|
|
18
|
+
// Training pixels: every opaque pixel of every layer (sampled if there are many).
|
|
19
|
+
const all = [];
|
|
20
|
+
for (const l of layers)
|
|
21
|
+
for (let i = 0; i < l.width * l.height; i++)
|
|
22
|
+
if (l.rgba[i * 4 + 3] >= 128)
|
|
23
|
+
all.push([l.rgba[i * 4], l.rgba[i * 4 + 1], l.rgba[i * 4 + 2]]);
|
|
24
|
+
const rand = random(all.length);
|
|
25
|
+
const sample = all.length > 30_000 ? Array.from({ length: 30_000 }, () => all[Math.floor(rand() * all.length)]) : all;
|
|
26
|
+
const fixed = FIXED.map((i) => [base.rgb[i * 3], base.rgb[i * 3 + 1], base.rgb[i * 3 + 2]]);
|
|
27
|
+
const nearest = (centers, r, g, b) => {
|
|
28
|
+
let best = 0, bestD = Infinity;
|
|
29
|
+
for (let c = 0; c < centers.length; c++) {
|
|
30
|
+
const k = centers[c];
|
|
31
|
+
const d = distance(r, g, b, k[0], k[1], k[2]);
|
|
32
|
+
if (d < bestD)
|
|
33
|
+
(bestD = d), (best = c);
|
|
34
|
+
}
|
|
35
|
+
return { index: best, d: bestD };
|
|
36
|
+
};
|
|
37
|
+
// k-means++ seeding of the free colours, around the fixed ones.
|
|
38
|
+
const free = [];
|
|
39
|
+
const dist = sample.map((p) => nearest(fixed, ...p).d);
|
|
40
|
+
const count = FREE_LAST - FREE_FIRST + 1;
|
|
41
|
+
while (free.length < count) {
|
|
42
|
+
const total = dist.reduce((a, b) => a + b, 0);
|
|
43
|
+
if (total === 0) {
|
|
44
|
+
free.push([0, 0, 0]); // the fixed colours already cover the art exactly
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
let t = rand() * total, pick = 0;
|
|
48
|
+
for (; pick < dist.length - 1 && (t -= dist[pick]) > 0; pick++)
|
|
49
|
+
;
|
|
50
|
+
const c = sample[pick];
|
|
51
|
+
free.push([...c]);
|
|
52
|
+
for (let i = 0; i < sample.length; i++)
|
|
53
|
+
dist[i] = Math.min(dist[i], distance(...sample[i], ...c));
|
|
54
|
+
}
|
|
55
|
+
// Lloyd iterations: only the free colours move.
|
|
56
|
+
for (let it = 0; it < iterations; it++) {
|
|
57
|
+
const sums = free.map(() => [0, 0, 0, 0]);
|
|
58
|
+
const centers = [...fixed, ...free];
|
|
59
|
+
for (const p of sample) {
|
|
60
|
+
const { index } = nearest(centers, ...p);
|
|
61
|
+
if (index < fixed.length)
|
|
62
|
+
continue;
|
|
63
|
+
const s = sums[index - fixed.length];
|
|
64
|
+
s[0] += p[0];
|
|
65
|
+
s[1] += p[1];
|
|
66
|
+
s[2] += p[2];
|
|
67
|
+
s[3]++;
|
|
68
|
+
}
|
|
69
|
+
sums.forEach((s, i) => {
|
|
70
|
+
if (s[3])
|
|
71
|
+
free[i] = [Math.round(s[0] / s[3]), Math.round(s[1] / s[3]), Math.round(s[2] / s[3])];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const centers = [...fixed, ...free];
|
|
75
|
+
const indexOf = [...FIXED, ...Array.from({ length: count }, (_, i) => FREE_FIRST + i)];
|
|
76
|
+
const map = (layer) => {
|
|
77
|
+
const { width: w, height: h, rgba } = layer;
|
|
78
|
+
const out = new Uint8Array(w * h).fill(SKIP);
|
|
79
|
+
const err = new Float32Array(w * h * 3);
|
|
80
|
+
for (let y = 0; y < h; y++) {
|
|
81
|
+
for (let x = 0; x < w; x++) {
|
|
82
|
+
const i = y * w + x;
|
|
83
|
+
if (rgba[i * 4 + 3] < 128)
|
|
84
|
+
continue;
|
|
85
|
+
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]);
|
|
86
|
+
const { index } = nearest(centers, r, g, b);
|
|
87
|
+
out[i] = indexOf[index];
|
|
88
|
+
if (!dither)
|
|
89
|
+
continue;
|
|
90
|
+
const c = centers[index];
|
|
91
|
+
const e = [r - c[0], g - c[1], b - c[2]];
|
|
92
|
+
const spread = (dx, dy, f) => {
|
|
93
|
+
const xx = x + dx, yy = y + dy;
|
|
94
|
+
if (xx < 0 || xx >= w || yy >= h)
|
|
95
|
+
return;
|
|
96
|
+
const j = yy * w + xx;
|
|
97
|
+
if (rgba[j * 4 + 3] < 128)
|
|
98
|
+
return;
|
|
99
|
+
for (let k = 0; k < 3; k++)
|
|
100
|
+
err[j * 3 + k] += e[k] * f;
|
|
101
|
+
};
|
|
102
|
+
spread(1, 0, 7 / 16);
|
|
103
|
+
spread(-1, 1, 3 / 16);
|
|
104
|
+
spread(0, 1, 5 / 16);
|
|
105
|
+
spread(1, 1, 1 / 16);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
};
|
|
110
|
+
const cels = layers.map((l) => {
|
|
111
|
+
let pixels = map(l);
|
|
112
|
+
let [x, y, w, h] = [l.x ?? 0, l.y ?? 0, l.width, l.height];
|
|
113
|
+
if (l.priority > -1000) {
|
|
114
|
+
// Foreground pieces: crop to what's opaque.
|
|
115
|
+
let x0 = w, y0 = h, x1 = -1, y1 = -1;
|
|
116
|
+
for (let yy = 0; yy < h; yy++)
|
|
117
|
+
for (let xx = 0; xx < w; xx++)
|
|
118
|
+
if (pixels[yy * w + xx] !== SKIP)
|
|
119
|
+
(x0 = Math.min(x0, xx)), (x1 = Math.max(x1, xx)), (y0 = Math.min(y0, yy)), (y1 = Math.max(y1, yy));
|
|
120
|
+
if (x1 < 0)
|
|
121
|
+
throw new Error(`Layer at priority ${l.priority} is fully transparent`);
|
|
122
|
+
const cw = x1 - x0 + 1, ch = y1 - y0 + 1;
|
|
123
|
+
const cropped = new Uint8Array(cw * ch);
|
|
124
|
+
for (let yy = 0; yy < ch; yy++)
|
|
125
|
+
cropped.set(pixels.subarray((y0 + yy) * w + x0, (y0 + yy) * w + x0 + cw), yy * cw);
|
|
126
|
+
[pixels, x, y, w, h] = [cropped, x + x0, y + y0, cw, ch];
|
|
127
|
+
}
|
|
128
|
+
return { width: w, height: h, displaceX: 0, displaceY: 0, skipColor: SKIP, pixels, unknown16: 2768, priority: l.priority, x, y };
|
|
129
|
+
});
|
|
130
|
+
const rgb = [];
|
|
131
|
+
for (let i = 0; i < 255; i++) {
|
|
132
|
+
const k = i >= FREE_FIRST && i <= FREE_LAST ? free[i - FREE_FIRST] : [base.rgb[i * 3], base.rgb[i * 3 + 1], base.rgb[i * 3 + 2]];
|
|
133
|
+
rgb.push(k);
|
|
134
|
+
}
|
|
135
|
+
const palette = {
|
|
136
|
+
header: Uint8Array.from([14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]),
|
|
137
|
+
entryPrefix: Uint8Array.from([0, 255, 255, 255, 255, 255, 255, 255, 0, 0]),
|
|
138
|
+
first: 0,
|
|
139
|
+
used: rgb.map(() => 1),
|
|
140
|
+
rgb,
|
|
141
|
+
shared: false,
|
|
142
|
+
defaultUsed: 1,
|
|
143
|
+
};
|
|
144
|
+
return { resolution: [320, 200], cels, palette };
|
|
145
|
+
}
|
|
146
|
+
const clamp = (v) => (v < 0 ? 0 : v > 255 ? 255 : v);
|
|
147
|
+
/** The colours a view may use: shared character colours (+ fixed ones), or a room's too. */
|
|
148
|
+
export const SHARED_COLORS = FIXED;
|
|
149
|
+
export const ROOM_COLORS = [...FIXED, ...Array.from({ length: FREE_LAST - FREE_FIRST + 1 }, (_, i) => FREE_FIRST + i)];
|
|
150
|
+
export const SHADOW_COLOR = 254;
|
|
151
|
+
/**
|
|
152
|
+
* Maps RGBA art onto existing palette entries (no new colours): for sprites that must live
|
|
153
|
+
* with a room's or the shared palette. Alpha below 64 is transparent (`skip`), 64-191 is a
|
|
154
|
+
* shadow (the remap colour 254, which darkens what's behind), 192+ is opaque.
|
|
155
|
+
*/
|
|
156
|
+
export function mapToPalette(rgba, width, height, palette, indices, skip, dither = false) {
|
|
157
|
+
const centers = indices.map((i) => [palette.rgb[i * 3], palette.rgb[i * 3 + 1], palette.rgb[i * 3 + 2]]);
|
|
158
|
+
const out = new Uint8Array(width * height).fill(skip);
|
|
159
|
+
const err = new Float32Array(width * height * 3);
|
|
160
|
+
const cache = new Map();
|
|
161
|
+
for (let y = 0; y < height; y++) {
|
|
162
|
+
for (let x = 0; x < width; x++) {
|
|
163
|
+
const i = y * width + x;
|
|
164
|
+
const a = rgba[i * 4 + 3];
|
|
165
|
+
if (a < 64)
|
|
166
|
+
continue;
|
|
167
|
+
if (a < 192) {
|
|
168
|
+
out[i] = SHADOW_COLOR;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
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]);
|
|
172
|
+
const key = (Math.round(r) << 16) | (Math.round(g) << 8) | Math.round(b);
|
|
173
|
+
let best = cache.get(key);
|
|
174
|
+
if (best === undefined) {
|
|
175
|
+
let bestD = Infinity;
|
|
176
|
+
best = 0;
|
|
177
|
+
centers.forEach((c, k) => {
|
|
178
|
+
const d = distance(r, g, b, c[0], c[1], c[2]);
|
|
179
|
+
if (d < bestD)
|
|
180
|
+
(bestD = d), (best = k);
|
|
181
|
+
});
|
|
182
|
+
cache.set(key, best);
|
|
183
|
+
}
|
|
184
|
+
out[i] = indices[best];
|
|
185
|
+
if (!dither)
|
|
186
|
+
continue;
|
|
187
|
+
const c = centers[best];
|
|
188
|
+
const e = [r - c[0], g - c[1], b - c[2]];
|
|
189
|
+
for (const [dx, dy, f] of [[1, 0, 7 / 16], [-1, 1, 3 / 16], [0, 1, 5 / 16], [1, 1, 1 / 16]]) {
|
|
190
|
+
const xx = x + dx, yy = y + dy;
|
|
191
|
+
if (xx < 0 || xx >= width || yy >= height)
|
|
192
|
+
continue;
|
|
193
|
+
for (let k = 0; k < 3; k++)
|
|
194
|
+
err[(yy * width + xx) * 3 + k] += e[k] * f;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|