brs-js 3.1.0 → 3.2.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/README.md +212 -86
- package/dist/dist.js +5 -5
- package/dist/dist.js.map +1 -1
- package/dist/dist.mjs +527 -189
- package/dist/dist.mjs.map +1 -1
- package/dist/dist.node.js +5 -5
- package/dist/dist.node.js.map +1 -1
- package/dist/dist.web.js +5 -5
- package/dist/dist.web.js.map +1 -1
- package/dist/src/brdb/brdb.d.ts +4 -3
- package/dist/src/brdb/brdb.d.ts.map +1 -1
- package/dist/src/brdb/index.d.ts +2 -2
- package/dist/src/brdb/index.d.ts.map +1 -1
- package/dist/src/brdb/msgpack.d.ts.map +1 -1
- package/dist/src/brdb/world.d.ts +163 -3
- package/dist/src/brdb/world.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# brs.js
|
|
2
2
|
|
|
3
|
-
Read and write Brickadia save files (.
|
|
4
|
-
|
|
5
|
-
Currently supports all BRS save versions. BRS Files are no longer supported as of Brickadia EA3.
|
|
3
|
+
Read and write Brickadia save files (.brz, .brdb, .brs)
|
|
6
4
|
|
|
7
5
|
**Warning:** **Unreal Engine uses numbers potentially larger than Javascript can handle.**
|
|
8
6
|
|
|
@@ -28,7 +26,217 @@ Script: `window.BRS`
|
|
|
28
26
|
|
|
29
27
|
### Examples
|
|
30
28
|
|
|
31
|
-
Examples are available in the `examples/` directory. All `.
|
|
29
|
+
Examples are available in the `examples/` directory. All `.mjs` examples are for node, `.html` are for web.
|
|
30
|
+
|
|
31
|
+
## Brickadia Worlds (.brz, .brdb)
|
|
32
|
+
|
|
33
|
+
brs-js reads and writes both Brickadia World formats — the `.brz` archive
|
|
34
|
+
and the live `.brdb` SQLite database ([see below](#brdb-sqlite-worlds)) —
|
|
35
|
+
covering bricks, components, wires, entities, multiple grids
|
|
36
|
+
(microchips), and prefab metadata. There are two writers, and each can
|
|
37
|
+
target either format:
|
|
38
|
+
|
|
39
|
+
- the `World` builder — author worlds from scratch, including sub-grids,
|
|
40
|
+
entities, microchips, and prefabs (`w.toBrz()` or `db.save(desc, w)`);
|
|
41
|
+
- `writeBrzLegacy` — convert the same legacy `WriteSaveObject` that
|
|
42
|
+
`write()` takes, except that `components` on bricks is a modern typed
|
|
43
|
+
array (the legacy .brs component map is not converted) and `wires` on
|
|
44
|
+
the save uses modern component and port names.
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import { writeBrzLegacy, WorldReader } from 'brs-js';
|
|
48
|
+
|
|
49
|
+
const brz = writeBrzLegacy({
|
|
50
|
+
description: 'my world',
|
|
51
|
+
bricks: [{ size: [5, 5, 6], position: [0, 0, 6], color: [255, 0, 0] }],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// WorldReader is lazy: schemas are parsed once and cached, and chunk
|
|
55
|
+
// payloads are only decoded when you ask for them.
|
|
56
|
+
const world = WorldReader.from(brz);
|
|
57
|
+
world.bundle(); // Meta/Bundle.json
|
|
58
|
+
world.brickAssets(); // basic asset names followed by procedural
|
|
59
|
+
world.brickOwners(); // owner rows (PUBLIC excluded)
|
|
60
|
+
world.gridIds(); // [1] plus any entity sub-grids
|
|
61
|
+
for (const brick of world.bricks()) {
|
|
62
|
+
// decoded chunk by chunk
|
|
63
|
+
}
|
|
64
|
+
for (const entity of world.entities()) {
|
|
65
|
+
// dynamic grids, microchip grids, ...
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### World builder
|
|
70
|
+
|
|
71
|
+
`World` mirrors the reference Rust crate's `wrapper::World`: add bricks,
|
|
72
|
+
components, wires, entities, and microchip grids, then serialize with
|
|
73
|
+
`toBrz()`. Brick assets and materials are referenced by name, and the
|
|
74
|
+
generated `COMPONENTS`/`BRICK_ASSETS` catalogs provide typo-safe names
|
|
75
|
+
with editor completion.
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
import { Brdb, World, brdb } from 'brs-js';
|
|
79
|
+
const { COMPONENTS, BRICK_ASSETS } = brdb;
|
|
80
|
+
|
|
81
|
+
const w = new World();
|
|
82
|
+
|
|
83
|
+
// Main-grid bricks. addBrick returns a handle used by wires and links.
|
|
84
|
+
const light = w.addBrick({
|
|
85
|
+
position: [0, 0, 6], // default asset is a 1x1 PB_DefaultBrick
|
|
86
|
+
color: [255, 255, 255],
|
|
87
|
+
components: [
|
|
88
|
+
// omitted data fields take the game's default values
|
|
89
|
+
{ type: COMPONENTS.PointLight.NAME, data: { Brightness: 200 } },
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
w.addBrick({
|
|
93
|
+
asset: BRICK_ASSETS.B_2x2F_Round,
|
|
94
|
+
position: [20, 0, 2],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// serialize to a .brz archive...
|
|
98
|
+
const bytes = w.toBrz({ bundle: { description: 'My World' } });
|
|
99
|
+
|
|
100
|
+
// ...or write the same World into a .brdb as a revision
|
|
101
|
+
const db = await Brdb.openOrCreate('My World.brdb');
|
|
102
|
+
db.save('built with brs-js', w);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Wires connect component ports by name; endpoints may be in different
|
|
106
|
+
grids or chunks (the writer emits remote wire rows automatically):
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
const TARGET = COMPONENTS.Target;
|
|
110
|
+
const PRINT = COMPONENTS.Exec_PrintToConsole;
|
|
111
|
+
|
|
112
|
+
const sensor = w.addBrick({
|
|
113
|
+
asset: TARGET.BRICK,
|
|
114
|
+
position: [0, 0, 6],
|
|
115
|
+
components: [{ type: TARGET.NAME }],
|
|
116
|
+
});
|
|
117
|
+
const printer = w.addBrick({
|
|
118
|
+
asset: PRINT.BRICK,
|
|
119
|
+
position: [20, 0, 2],
|
|
120
|
+
components: [{ type: PRINT.NAME, data: { Text: 'hit!' } }],
|
|
121
|
+
});
|
|
122
|
+
w.addWire(
|
|
123
|
+
{ brick: sensor, component_type: TARGET.NAME, port: TARGET.PORTS.bJustHit },
|
|
124
|
+
{ brick: printer, component_type: PRINT.NAME, port: PRINT.PORTS.Exec }
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Microchips place an outer shell brick linked to an inner brick grid.
|
|
129
|
+
`makePrefab` turns the bundle into a prefab (`Meta/Prefab.json` with
|
|
130
|
+
pivots/bounds from the main-grid bricks) so the `.brz` pastes like a
|
|
131
|
+
native copied selection — see `examples/writeMicrochip.mjs`:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
const CHIP_IN = COMPONENTS.MicrochipInput;
|
|
135
|
+
const CHIP_OUT = COMPONENTS.MicrochipOutput;
|
|
136
|
+
|
|
137
|
+
const w = new World();
|
|
138
|
+
const { grid } = w.addMicrochip({ position: [0, 0, 2] });
|
|
139
|
+
|
|
140
|
+
// chip contents live in the inner grid (positions are grid-local)
|
|
141
|
+
const input = w.addBrick(
|
|
142
|
+
{
|
|
143
|
+
asset: CHIP_IN.BRICK,
|
|
144
|
+
position: [0, 0, 2],
|
|
145
|
+
components: [{ type: CHIP_IN.NAME, data: { PortLabel: 'In' } }],
|
|
146
|
+
},
|
|
147
|
+
grid
|
|
148
|
+
);
|
|
149
|
+
const output = w.addBrick(
|
|
150
|
+
{
|
|
151
|
+
asset: CHIP_OUT.BRICK,
|
|
152
|
+
position: [20, 0, 2],
|
|
153
|
+
components: [{ type: CHIP_OUT.NAME, data: { PortLabel: 'Out' } }],
|
|
154
|
+
},
|
|
155
|
+
grid
|
|
156
|
+
);
|
|
157
|
+
w.addWire(
|
|
158
|
+
{ brick: input, component_type: CHIP_IN.NAME, port: CHIP_IN.PORTS.RER_Output },
|
|
159
|
+
{ brick: output, component_type: CHIP_OUT.NAME, port: CHIP_OUT.PORTS.RER_Input }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
w.makePrefab({ isMicrochipPrefab: true });
|
|
163
|
+
writeFileSync('chip.brz', w.toBrz());
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Standalone entities and dynamic sub-grids:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
const owner = w.addOwner({ id: '...uuid...', name: 'cake' });
|
|
170
|
+
|
|
171
|
+
// a frozen dynamic brick grid floating at Z 40, holding one brick
|
|
172
|
+
const grid = w.addBrickGrid({ frozen: true, location: { X: 0, Y: 0, Z: 40 } });
|
|
173
|
+
w.addBrick({ position: [0, 0, 3], owner_index: owner }, grid);
|
|
174
|
+
|
|
175
|
+
// or a bare entity
|
|
176
|
+
w.addEntity({ type: 'Entity_DynamicBrickGrid', frozen: true });
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Compression
|
|
180
|
+
|
|
181
|
+
Blobs are stored uncompressed by default, which is valid and matches the
|
|
182
|
+
reference implementation byte for byte. To compress with zstd (Node 22.15
|
|
183
|
+
or newer):
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
import { zstdCompressSync, constants } from 'node:zlib';
|
|
187
|
+
const brz = writeBrzLegacy(save, {
|
|
188
|
+
compress: data =>
|
|
189
|
+
zstdCompressSync(data, {
|
|
190
|
+
params: { [constants.ZSTD_c_compressionLevel]: 14 },
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Reading notes
|
|
196
|
+
|
|
197
|
+
Reading applies a few normalizations:
|
|
198
|
+
|
|
199
|
+
- Brick order round-trips per chunk rather than in global save order.
|
|
200
|
+
- `collision.tool` always reads `true` because the format does not store it.
|
|
201
|
+
- Palette color indices are resolved to `[r, g, b]` at write time.
|
|
202
|
+
- `save.map` is not mapped; pass `{ environment }` in the options instead.
|
|
203
|
+
|
|
204
|
+
The cross-language fixture tests skip unless fixtures have been generated.
|
|
205
|
+
To enable them, run `just fixtures` with a crate checkout at `../brdb` (or
|
|
206
|
+
set `BRDB_CRATE`).
|
|
207
|
+
|
|
208
|
+
### .brdb (SQLite worlds)
|
|
209
|
+
|
|
210
|
+
`.brdb` is the game's live world format: the same virtual filesystem as
|
|
211
|
+
`.brz`, stored in SQLite with full revision history. Reading and writing
|
|
212
|
+
requires `better-sqlite3` (an optional peer dependency, node only):
|
|
213
|
+
|
|
214
|
+
npm install better-sqlite3
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
import { Brdb } from 'brs-js';
|
|
218
|
+
|
|
219
|
+
const db = await Brdb.open('MyWorld.brdb');
|
|
220
|
+
const reader = db.worldReader(); // same surface as WorldReader.from(brz)
|
|
221
|
+
for (const brick of reader.bricks()) console.log(brick.position);
|
|
222
|
+
|
|
223
|
+
const out = await Brdb.create('New.brdb');
|
|
224
|
+
// save() takes a World builder instance or a legacy save object
|
|
225
|
+
out.save('initial save', {
|
|
226
|
+
bricks: [{ size: [5, 5, 6], position: [0, 0, 6] }],
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Every `save`/`writePending` call is one revision: unchanged files are shared,
|
|
231
|
+
changed files are soft-deleted and reinserted, and blobs are deduplicated by
|
|
232
|
+
BLAKE3 hash. Blobs compress with zstd level 14 when node's zlib supports it
|
|
233
|
+
(pass `compress: null` to store raw). Advanced: `new Brdb(db)` wraps any
|
|
234
|
+
SQLite handle with a compatible synchronous API, with no dependency at all.
|
|
235
|
+
|
|
236
|
+
## Legacy saves (.brs)
|
|
237
|
+
|
|
238
|
+
Supports all BRS save versions. BRS files are no longer supported by the
|
|
239
|
+
game as of Brickadia EA3 — use `.brz`/`.brdb` above for current builds.
|
|
32
240
|
|
|
33
241
|
### Save Object
|
|
34
242
|
|
|
@@ -339,88 +547,6 @@ Notes:
|
|
|
339
547
|
| B_1x1_Gate_Equal | |
|
|
340
548
|
| B_1x1_Gate_NOT_Bitwise | |
|
|
341
549
|
|
|
342
|
-
## Brickadia Worlds (.brz)
|
|
343
|
-
|
|
344
|
-
brs-js can write and read the Brickadia World archive format (`.brz`).
|
|
345
|
-
Writing covers bricks, components, and wires; entities can be read but not
|
|
346
|
-
yet written. `writeBrzLegacy` converts the same legacy `WriteSaveObject`
|
|
347
|
-
that `write()` takes, except that `components` on bricks is a modern typed
|
|
348
|
-
array (the legacy .brs component map is not converted) and `wires` on the
|
|
349
|
-
save uses modern component and port names. A builder-style world API for
|
|
350
|
-
authoring saves from scratch is planned:
|
|
351
|
-
|
|
352
|
-
```js
|
|
353
|
-
import { writeBrzLegacy, WorldReader } from 'brs-js';
|
|
354
|
-
|
|
355
|
-
const brz = writeBrzLegacy({
|
|
356
|
-
description: 'my world',
|
|
357
|
-
bricks: [{ size: [5, 5, 6], position: [0, 0, 6], color: [255, 0, 0] }],
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
// WorldReader is lazy: schemas are parsed once and cached, and chunk
|
|
361
|
-
// payloads are only decoded when you ask for them.
|
|
362
|
-
const world = WorldReader.from(brz);
|
|
363
|
-
world.bundle(); // Meta/Bundle.json
|
|
364
|
-
world.brickAssets(); // basic asset names followed by procedural
|
|
365
|
-
world.brickOwners(); // owner rows (PUBLIC excluded)
|
|
366
|
-
world.gridIds(); // [1] plus any entity sub-grids
|
|
367
|
-
for (const brick of world.bricks()) {
|
|
368
|
-
// decoded chunk by chunk
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
Blobs are stored uncompressed by default, which is valid and matches the
|
|
373
|
-
reference implementation byte for byte. To compress with zstd (Node 22.15
|
|
374
|
-
or newer):
|
|
375
|
-
|
|
376
|
-
```js
|
|
377
|
-
import { zstdCompressSync, constants } from 'node:zlib';
|
|
378
|
-
const brz = writeBrzLegacy(save, {
|
|
379
|
-
compress: data =>
|
|
380
|
-
zstdCompressSync(data, {
|
|
381
|
-
params: { [constants.ZSTD_c_compressionLevel]: 14 },
|
|
382
|
-
}),
|
|
383
|
-
});
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
Reading applies a few normalizations:
|
|
387
|
-
|
|
388
|
-
- Brick order round-trips per chunk rather than in global save order.
|
|
389
|
-
- `collision.tool` always reads `true` because the format does not store it.
|
|
390
|
-
- Palette color indices are resolved to `[r, g, b]` at write time.
|
|
391
|
-
- `save.map` is not mapped; pass `{ environment }` in the options instead.
|
|
392
|
-
|
|
393
|
-
The cross-language fixture tests skip unless fixtures have been generated.
|
|
394
|
-
To enable them, run `just fixtures` with a crate checkout at `../brdb` (or
|
|
395
|
-
set `BRDB_CRATE`).
|
|
396
|
-
|
|
397
|
-
### .brdb (SQLite worlds)
|
|
398
|
-
|
|
399
|
-
`.brdb` is the game's live world format: the same virtual filesystem as
|
|
400
|
-
`.brz`, stored in SQLite with full revision history. Reading and writing
|
|
401
|
-
requires `better-sqlite3` (an optional peer dependency, node only):
|
|
402
|
-
|
|
403
|
-
npm install better-sqlite3
|
|
404
|
-
|
|
405
|
-
```js
|
|
406
|
-
import { Brdb } from 'brs-js';
|
|
407
|
-
|
|
408
|
-
const db = await Brdb.open('MyWorld.brdb');
|
|
409
|
-
const reader = db.worldReader(); // same surface as WorldReader.from(brz)
|
|
410
|
-
for (const brick of reader.bricks()) console.log(brick.position);
|
|
411
|
-
|
|
412
|
-
const out = await Brdb.create('New.brdb');
|
|
413
|
-
out.save('initial save', {
|
|
414
|
-
bricks: [{ size: [5, 5, 6], position: [0, 0, 6] }],
|
|
415
|
-
});
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
Every `save`/`writePending` call is one revision: unchanged files are shared,
|
|
419
|
-
changed files are soft-deleted and reinserted, and blobs are deduplicated by
|
|
420
|
-
BLAKE3 hash. Blobs compress with zstd level 14 when node's zlib supports it
|
|
421
|
-
(pass `compress: null` to store raw). Advanced: `new Brdb(db)` wraps any
|
|
422
|
-
SQLite handle with a compatible synchronous API, with no dependency at all.
|
|
423
|
-
|
|
424
550
|
## Development
|
|
425
551
|
|
|
426
552
|
NPM Scripts (`npm run <cmd>`)
|