sci2-ts 0.14.0 → 0.15.0
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 +6 -0
- package/lib/991.sc +44 -30
- package/lib/993.sc +40 -6
- package/lib/996.sc +8 -3
- package/package.json +1 -1
- package/packages/content/src/room.js +46 -3
- package/packages/content/src/room.ts +41 -3
- package/packages/content/src/target.d.ts +9 -0
- package/packages/content/src/target.ts +5 -0
- package/packages/sci/src/index.d.ts +1 -0
- package/packages/sci/src/index.js +1 -0
- package/packages/sci/src/index.ts +1 -0
- package/packages/sci/src/text/font.d.ts +19 -2
- package/packages/sci/src/text/font.js +62 -14
- package/packages/sci/src/text/font.ts +64 -15
- package/packages/sci/src/text/styled.d.ts +40 -0
- package/packages/sci/src/text/styled.js +92 -0
- package/packages/sci/src/text/styled.ts +110 -0
- package/packages/sci/src/vm/kernels/text.js +40 -29
- package/packages/sci/src/vm/kernels/text.ts +41 -28
- package/tools/art/build.d.ts +9 -3
- package/tools/art/build.js +33 -2
- package/tools/art/build.ts +27 -4
- package/tools/art/font.d.ts +20 -0
- package/tools/art/font.js +35 -0
- package/tools/art/font.ts +41 -0
- package/tools/game/build.d.ts +3 -1
- package/tools/game/build.js +13 -1
- package/tools/game/build.ts +17 -3
package/tools/art/build.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, resolve } from "node:path";
|
|
|
3
3
|
import { ResourceType, writeFont, writeHunkPalette, writePic, writeView } from "../../packages/sci/src/index.js";
|
|
4
4
|
import { basePalette } from "../game/defaults.js";
|
|
5
5
|
import { decodePng } from "../png.js";
|
|
6
|
-
import { FontSheetError, fontFromSheet } from "./font.js";
|
|
6
|
+
import { FontSheetError, fontFromMetrics, fontFromSheet } from "./font.js";
|
|
7
7
|
export class ArtError extends Error {
|
|
8
8
|
}
|
|
9
9
|
export function buildArt(file) {
|
|
@@ -173,8 +173,39 @@ export function buildArt(file) {
|
|
|
173
173
|
});
|
|
174
174
|
list(m.fonts ?? [], "fonts", 0, 65536).forEach((value, i) => {
|
|
175
175
|
const where = `fonts[${i}]`;
|
|
176
|
-
const f = object(value, where, ["number", "png", "cell", "first", "spacing", "space", "lineHeight"]);
|
|
176
|
+
const f = object(value, where, ["number", "png", "cell", "first", "spacing", "space", "lineHeight", "metrics"]);
|
|
177
177
|
const n = number(f.number, "font", `${where}.number`);
|
|
178
|
+
// A bitmap font with its own metrics: a sidecar JSON naming its atlas.
|
|
179
|
+
if (f.metrics !== undefined) {
|
|
180
|
+
if (f.png !== undefined || f.cell !== undefined)
|
|
181
|
+
return fail(where, "a font is a sheet (png, cell) or has metrics, not both");
|
|
182
|
+
const metricsFile = path(f.metrics, `${where}.metrics`);
|
|
183
|
+
let metrics;
|
|
184
|
+
try {
|
|
185
|
+
metrics = JSON.parse(readFileSync(metricsFile, "utf8"));
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
return fail(`${where}.metrics`, `${metricsFile}: ${e.message}`);
|
|
189
|
+
}
|
|
190
|
+
const atlas = resolve(dirname(metricsFile), String(metrics.atlas));
|
|
191
|
+
let img;
|
|
192
|
+
try {
|
|
193
|
+
img = decodePng(readFileSync(atlas));
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
return fail(`${where}.metrics`, `${atlas}: ${e.message}`);
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
resources.push({ type: ResourceType.Font, number: n, data: writeFont(fontFromMetrics(img, metrics)) });
|
|
200
|
+
images++;
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
if (!(e instanceof FontSheetError))
|
|
204
|
+
throw e;
|
|
205
|
+
fail(`${where}.metrics`, e.message);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
178
209
|
const filename = path(f.png, `${where}.png`);
|
|
179
210
|
let img;
|
|
180
211
|
try {
|
package/tools/art/build.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, resolve } from "node:path";
|
|
|
3
3
|
import { ResourceType, writeFont, writeHunkPalette, writePic, writeView, type ResourceData, type ViewLoop } from "../../packages/sci/src/index.ts";
|
|
4
4
|
import { basePalette } from "../game/defaults.ts";
|
|
5
5
|
import { decodePng } from "../png.ts";
|
|
6
|
-
import { FontSheetError, fontFromSheet } from "./font.ts";
|
|
6
|
+
import { FontSheetError, fontFromMetrics, fontFromSheet, type FontMetrics } from "./font.ts";
|
|
7
7
|
|
|
8
8
|
/** Editor-neutral, exact-colour art for standalone games. See docs/art-workflow.md. */
|
|
9
9
|
export interface ArtManifest {
|
|
@@ -13,8 +13,11 @@ export interface ArtManifest {
|
|
|
13
13
|
maxColours?: number;
|
|
14
14
|
pictures: { number: number; layers: { png: string; priority: number }[] }[];
|
|
15
15
|
views: { number: number; loops: ({ cels: { png: string; anchor: [number, number] }[] } | { link: number; mirror: boolean })[] }[];
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Fonts (tools/art/font.ts): drawn as sheets of glyphs, or a bitmap font with its own
|
|
18
|
+
* metrics, a JSON file (bearings, advances, its atlas) exported from the font's source.
|
|
19
|
+
*/
|
|
20
|
+
fonts?: ({ number: number; png: string; cell: [number, number]; first?: number; spacing?: number; space?: number; lineHeight?: number } | { number: number; metrics: string })[];
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export class ArtError extends Error {}
|
|
@@ -153,8 +156,28 @@ export function buildArt(file: string): { resources: ResourceData[]; colours: st
|
|
|
153
156
|
});
|
|
154
157
|
list(m.fonts ?? [], "fonts", 0, 65536).forEach((value, i) => {
|
|
155
158
|
const where = `fonts[${i}]`;
|
|
156
|
-
const f = object(value, where, ["number", "png", "cell", "first", "spacing", "space", "lineHeight"]);
|
|
159
|
+
const f = object(value, where, ["number", "png", "cell", "first", "spacing", "space", "lineHeight", "metrics"]);
|
|
157
160
|
const n = number(f.number, "font", `${where}.number`);
|
|
161
|
+
// A bitmap font with its own metrics: a sidecar JSON naming its atlas.
|
|
162
|
+
if (f.metrics !== undefined) {
|
|
163
|
+
if (f.png !== undefined || f.cell !== undefined) return fail(where, "a font is a sheet (png, cell) or has metrics, not both");
|
|
164
|
+
const metricsFile = path(f.metrics, `${where}.metrics`);
|
|
165
|
+
let metrics: FontMetrics;
|
|
166
|
+
try { metrics = JSON.parse(readFileSync(metricsFile, "utf8")) as FontMetrics; }
|
|
167
|
+
catch (e) { return fail(`${where}.metrics`, `${metricsFile}: ${(e as Error).message}`); }
|
|
168
|
+
const atlas = resolve(dirname(metricsFile), String(metrics.atlas));
|
|
169
|
+
let img: ReturnType<typeof decodePng>;
|
|
170
|
+
try { img = decodePng(readFileSync(atlas)); }
|
|
171
|
+
catch (e) { return fail(`${where}.metrics`, `${atlas}: ${(e as Error).message}`); }
|
|
172
|
+
try {
|
|
173
|
+
resources.push({ type: ResourceType.Font, number: n, data: writeFont(fontFromMetrics(img, metrics)) });
|
|
174
|
+
images++;
|
|
175
|
+
} catch (e) {
|
|
176
|
+
if (!(e instanceof FontSheetError)) throw e;
|
|
177
|
+
fail(`${where}.metrics`, e.message);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
158
181
|
const filename = path(f.png, `${where}.png`);
|
|
159
182
|
let img: ReturnType<typeof decodePng>;
|
|
160
183
|
try { img = decodePng(readFileSync(filename)); }
|
package/tools/art/font.d.ts
CHANGED
|
@@ -22,3 +22,23 @@ export interface FontSheet {
|
|
|
22
22
|
export declare class FontSheetError extends Error {
|
|
23
23
|
}
|
|
24
24
|
export declare function fontFromSheet(img: Rgba, sheet: FontSheet): Font;
|
|
25
|
+
/**
|
|
26
|
+
* A font from a bitmap font's own metrics (sidecar JSON next to an atlas, as exported from
|
|
27
|
+
* BDF): each glyph's rectangle in the atlas, where its top sits below the line's top, its
|
|
28
|
+
* bearing from the pen and its advance. Nothing is trimmed, moved or respaced: the glyph is
|
|
29
|
+
* the rectangle's ink, with blank rows above it down to its top.
|
|
30
|
+
*/
|
|
31
|
+
export interface FontMetrics {
|
|
32
|
+
lineHeight: number;
|
|
33
|
+
first: number;
|
|
34
|
+
last: number;
|
|
35
|
+
atlas: string;
|
|
36
|
+
glyphs: {
|
|
37
|
+
code: number;
|
|
38
|
+
rect: [number, number, number, number];
|
|
39
|
+
bearingX: number;
|
|
40
|
+
top: number;
|
|
41
|
+
advance: number;
|
|
42
|
+
}[];
|
|
43
|
+
}
|
|
44
|
+
export declare function fontFromMetrics(img: Rgba, m: FontMetrics): Font;
|
package/tools/art/font.js
CHANGED
|
@@ -38,3 +38,38 @@ export function fontFromSheet(img, sheet) {
|
|
|
38
38
|
}
|
|
39
39
|
return { height: lineHeight, glyphs };
|
|
40
40
|
}
|
|
41
|
+
export function fontFromMetrics(img, m) {
|
|
42
|
+
const fail = (message) => { throw new FontSheetError(message); };
|
|
43
|
+
if (!Number.isInteger(m.lineHeight) || m.lineHeight < 1 || m.lineHeight > 255)
|
|
44
|
+
fail(`line height ${m.lineHeight} is outside 1..255`);
|
|
45
|
+
const glyphs = Array.from({ length: Math.max(128, m.last + 1) }, () => ({ width: 0, height: 0, pixels: new Uint8Array(0) }));
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
for (const g of m.glyphs) {
|
|
48
|
+
const [x, y, w, h] = g.rect;
|
|
49
|
+
if (!Number.isInteger(g.code) || g.code < 0 || g.code > 255)
|
|
50
|
+
fail(`character ${g.code} is outside 0..255`);
|
|
51
|
+
if (seen.has(g.code))
|
|
52
|
+
fail(`character ${g.code} is there twice`);
|
|
53
|
+
seen.add(g.code);
|
|
54
|
+
if (x < 0 || y < 0 || x + w > img.width || y + h > img.height)
|
|
55
|
+
fail(`character ${g.code}: its rectangle ${g.rect.join(",")} is outside the ${img.width}x${img.height} atlas`);
|
|
56
|
+
if (g.top < 0 || g.top + h > 255)
|
|
57
|
+
fail(`character ${g.code}: top ${g.top} with height ${h}`);
|
|
58
|
+
const height = g.top + h;
|
|
59
|
+
const pixels = new Uint8Array(w * height);
|
|
60
|
+
for (let gy = 0; gy < h; gy++) {
|
|
61
|
+
for (let gx = 0; gx < w; gx++) {
|
|
62
|
+
const a = img.data[((y + gy) * img.width + x + gx) * 4 + 3];
|
|
63
|
+
if (a !== 0 && a !== 255)
|
|
64
|
+
fail(`character ${g.code} (${x + gx},${y + gy}): alpha ${a}; the ink is opaque or clear`);
|
|
65
|
+
if (a)
|
|
66
|
+
pixels[(g.top + gy) * w + gx] = 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
glyphs[g.code] = { width: w, height, pixels, ...(g.bearingX ? { bearingX: g.bearingX } : {}), ...(g.advance !== w ? { advance: g.advance } : {}) };
|
|
70
|
+
}
|
|
71
|
+
for (let c = m.first; c <= m.last; c++)
|
|
72
|
+
if (!seen.has(c))
|
|
73
|
+
fail(`character ${c} is in ${m.first}..${m.last} but has no glyph`);
|
|
74
|
+
return { height: m.lineHeight, glyphs };
|
|
75
|
+
}
|
package/tools/art/font.ts
CHANGED
|
@@ -54,3 +54,44 @@ export function fontFromSheet(img: Rgba, sheet: FontSheet): Font {
|
|
|
54
54
|
}
|
|
55
55
|
return { height: lineHeight, glyphs };
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A font from a bitmap font's own metrics (sidecar JSON next to an atlas, as exported from
|
|
60
|
+
* BDF): each glyph's rectangle in the atlas, where its top sits below the line's top, its
|
|
61
|
+
* bearing from the pen and its advance. Nothing is trimmed, moved or respaced: the glyph is
|
|
62
|
+
* the rectangle's ink, with blank rows above it down to its top.
|
|
63
|
+
*/
|
|
64
|
+
export interface FontMetrics {
|
|
65
|
+
lineHeight: number;
|
|
66
|
+
first: number;
|
|
67
|
+
last: number;
|
|
68
|
+
atlas: string;
|
|
69
|
+
glyphs: { code: number; rect: [number, number, number, number]; bearingX: number; top: number; advance: number }[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function fontFromMetrics(img: Rgba, m: FontMetrics): Font {
|
|
73
|
+
const fail = (message: string): never => { throw new FontSheetError(message); };
|
|
74
|
+
if (!Number.isInteger(m.lineHeight) || m.lineHeight < 1 || m.lineHeight > 255) fail(`line height ${m.lineHeight} is outside 1..255`);
|
|
75
|
+
const glyphs: (Glyph | undefined)[] = Array.from({ length: Math.max(128, m.last + 1) }, () => ({ width: 0, height: 0, pixels: new Uint8Array(0) }));
|
|
76
|
+
const seen = new Set<number>();
|
|
77
|
+
for (const g of m.glyphs) {
|
|
78
|
+
const [x, y, w, h] = g.rect;
|
|
79
|
+
if (!Number.isInteger(g.code) || g.code < 0 || g.code > 255) fail(`character ${g.code} is outside 0..255`);
|
|
80
|
+
if (seen.has(g.code)) fail(`character ${g.code} is there twice`);
|
|
81
|
+
seen.add(g.code);
|
|
82
|
+
if (x < 0 || y < 0 || x + w > img.width || y + h > img.height) fail(`character ${g.code}: its rectangle ${g.rect.join(",")} is outside the ${img.width}x${img.height} atlas`);
|
|
83
|
+
if (g.top < 0 || g.top + h > 255) fail(`character ${g.code}: top ${g.top} with height ${h}`);
|
|
84
|
+
const height = g.top + h;
|
|
85
|
+
const pixels = new Uint8Array(w * height);
|
|
86
|
+
for (let gy = 0; gy < h; gy++) {
|
|
87
|
+
for (let gx = 0; gx < w; gx++) {
|
|
88
|
+
const a = img.data[((y + gy) * img.width + x + gx) * 4 + 3]!;
|
|
89
|
+
if (a !== 0 && a !== 255) fail(`character ${g.code} (${x + gx},${y + gy}): alpha ${a}; the ink is opaque or clear`);
|
|
90
|
+
if (a) pixels[(g.top + gy) * w + gx] = 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
glyphs[g.code] = { width: w, height, pixels, ...(g.bearingX ? { bearingX: g.bearingX } : {}), ...(g.advance !== w ? { advance: g.advance } : {}) };
|
|
94
|
+
}
|
|
95
|
+
for (let c = m.first; c <= m.last; c++) if (!seen.has(c)) fail(`character ${c} is in ${m.first}..${m.last} but has no glyph`);
|
|
96
|
+
return { height: m.lineHeight, glyphs };
|
|
97
|
+
}
|
package/tools/game/build.d.ts
CHANGED
|
@@ -20,7 +20,9 @@ import { type SpokenLine } from "../../packages/content/src/index.ts";
|
|
|
20
20
|
* voices/lines.json, the script, up to date
|
|
21
21
|
* games/<name>/resources.ts optional: `export default () => ResourceData[]` for art,
|
|
22
22
|
* music and anything else made by code
|
|
23
|
-
* games/<name>/game.json optional: { "library": false } to build without lib
|
|
23
|
+
* games/<name>/game.json optional: { "library": false } to build without lib/;
|
|
24
|
+
* "fonts": { "bold": 3, "italic": 4, "boldItalic": 5 }
|
|
25
|
+
* for the rooms' [b] and [i] markup
|
|
24
26
|
*
|
|
25
27
|
* plus the class library (lib/: its scripts and resources) and font 0, palette 999 and cursor
|
|
26
28
|
* view 999 (tools/game/defaults.ts), unless the game makes its own. Selectors are numbered as the scripts use them, starting with the nine object
|
package/tools/game/build.js
CHANGED
|
@@ -20,6 +20,17 @@ import { ITEMS_SCRIPT, libraryTarget } from "./target.js";
|
|
|
20
20
|
const HEADER = OBJECT_HEADER;
|
|
21
21
|
export class GameBuildError extends Error {
|
|
22
22
|
}
|
|
23
|
+
/** game.json's fonts for the rooms' markup: bold, italic and boldItalic font numbers. */
|
|
24
|
+
function gameFonts(value) {
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
const ok = (n) => Number.isInteger(n) && n >= 0 && n <= 65535;
|
|
28
|
+
const f = value;
|
|
29
|
+
if (typeof value !== "object" || value === null || !ok(f.bold) || !ok(f.italic) || !ok(f.boldItalic)) {
|
|
30
|
+
throw new GameBuildError(`game.json: "fonts" is { "bold": n, "italic": n, "boldItalic": n }, font numbers`);
|
|
31
|
+
}
|
|
32
|
+
return { bold: f.bold, italic: f.italic, boldItalic: f.boldItalic };
|
|
33
|
+
}
|
|
23
34
|
/** sci-ts's class library: scripts every game gets, and the resources they use. */
|
|
24
35
|
export const LIBRARY_DIR = resolve(import.meta.dirname, "../../lib");
|
|
25
36
|
export async function buildGame(dir, options = {}) {
|
|
@@ -27,6 +38,7 @@ export async function buildGame(dir, options = {}) {
|
|
|
27
38
|
if (!existsSync(scriptsDir))
|
|
28
39
|
throw new GameBuildError(`${dir}: no scripts/ folder`);
|
|
29
40
|
const config = existsSync(join(dir, "game.json")) ? JSON.parse(readFileSync(join(dir, "game.json"), "utf8")) : {};
|
|
41
|
+
const fonts = gameFonts(config.fonts);
|
|
30
42
|
const library = options.library ?? config.library ?? true;
|
|
31
43
|
const load = (from, re) => readdirSync(from).sort().filter((f) => re.test(f)).map((f) => ({ file: join(from, f), text: readFileSync(join(from, f), "utf8") }));
|
|
32
44
|
const sources = load(scriptsDir, /^\d+\.sca$/);
|
|
@@ -119,7 +131,7 @@ export async function buildGame(dir, options = {}) {
|
|
|
119
131
|
const roomsDir = join(dir, "rooms");
|
|
120
132
|
if (!existsSync(roomsDir))
|
|
121
133
|
return [];
|
|
122
|
-
const target = libraryTarget(globals, items);
|
|
134
|
+
const target = { ...libraryTarget(globals, items), ...(fonts ? { fonts } : {}) };
|
|
123
135
|
const flags = modFlags(dir, { content: target });
|
|
124
136
|
const out = [];
|
|
125
137
|
for (const { file, text } of load(roomsDir, /^\d+\.room\.yaml$/)) {
|
package/tools/game/build.ts
CHANGED
|
@@ -34,7 +34,9 @@ import { ITEMS_SCRIPT, libraryTarget } from "./target.ts";
|
|
|
34
34
|
* voices/lines.json, the script, up to date
|
|
35
35
|
* games/<name>/resources.ts optional: `export default () => ResourceData[]` for art,
|
|
36
36
|
* music and anything else made by code
|
|
37
|
-
* games/<name>/game.json optional: { "library": false } to build without lib
|
|
37
|
+
* games/<name>/game.json optional: { "library": false } to build without lib/;
|
|
38
|
+
* "fonts": { "bold": 3, "italic": 4, "boldItalic": 5 }
|
|
39
|
+
* for the rooms' [b] and [i] markup
|
|
38
40
|
*
|
|
39
41
|
* plus the class library (lib/: its scripts and resources) and font 0, palette 999 and cursor
|
|
40
42
|
* view 999 (tools/game/defaults.ts), unless the game makes its own. Selectors are numbered as the scripts use them, starting with the nine object
|
|
@@ -62,13 +64,25 @@ const HEADER = OBJECT_HEADER;
|
|
|
62
64
|
|
|
63
65
|
export class GameBuildError extends Error {}
|
|
64
66
|
|
|
67
|
+
/** game.json's fonts for the rooms' markup: bold, italic and boldItalic font numbers. */
|
|
68
|
+
function gameFonts(value: unknown): { bold: number; italic: number; boldItalic: number } | undefined {
|
|
69
|
+
if (value === undefined) return undefined;
|
|
70
|
+
const ok = (n: unknown) => Number.isInteger(n) && (n as number) >= 0 && (n as number) <= 65535;
|
|
71
|
+
const f = value as Record<string, unknown>;
|
|
72
|
+
if (typeof value !== "object" || value === null || !ok(f.bold) || !ok(f.italic) || !ok(f.boldItalic)) {
|
|
73
|
+
throw new GameBuildError(`game.json: "fonts" is { "bold": n, "italic": n, "boldItalic": n }, font numbers`);
|
|
74
|
+
}
|
|
75
|
+
return { bold: f.bold as number, italic: f.italic as number, boldItalic: f.boldItalic as number };
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
/** sci-ts's class library: scripts every game gets, and the resources they use. */
|
|
66
79
|
export const LIBRARY_DIR = resolve(import.meta.dirname, "../../lib");
|
|
67
80
|
|
|
68
81
|
export async function buildGame(dir: string, options: { library?: boolean } = {}): Promise<BuiltGame> {
|
|
69
82
|
const scriptsDir = join(dir, "scripts");
|
|
70
83
|
if (!existsSync(scriptsDir)) throw new GameBuildError(`${dir}: no scripts/ folder`);
|
|
71
|
-
const config = existsSync(join(dir, "game.json")) ? (JSON.parse(readFileSync(join(dir, "game.json"), "utf8")) as { library?: boolean }) : {};
|
|
84
|
+
const config = existsSync(join(dir, "game.json")) ? (JSON.parse(readFileSync(join(dir, "game.json"), "utf8")) as { library?: boolean; fonts?: unknown }) : {};
|
|
85
|
+
const fonts = gameFonts(config.fonts);
|
|
72
86
|
const library = options.library ?? config.library ?? true;
|
|
73
87
|
const load = (from: string, re: RegExp) =>
|
|
74
88
|
readdirSync(from).sort().filter((f) => re.test(f)).map((f) => ({ file: join(from, f), text: readFileSync(join(from, f), "utf8") }));
|
|
@@ -158,7 +172,7 @@ export async function buildGame(dir: string, options: { library?: boolean } = {}
|
|
|
158
172
|
const compileRooms = (): { file: string; text: string }[] => {
|
|
159
173
|
const roomsDir = join(dir, "rooms");
|
|
160
174
|
if (!existsSync(roomsDir)) return [];
|
|
161
|
-
const target = libraryTarget(globals, items);
|
|
175
|
+
const target = { ...libraryTarget(globals, items), ...(fonts ? { fonts } : {}) };
|
|
162
176
|
const flags = modFlags(dir, { content: target });
|
|
163
177
|
const out: { file: string; text: string }[] = [];
|
|
164
178
|
for (const { file, text } of load(roomsDir, /^\d+\.room\.yaml$/)) {
|