brs-js 1.4.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/.prettierrc +7 -0
  2. package/LICENSE +1 -1
  3. package/README.md +2 -2
  4. package/debug/Conf Spacestation.brs +0 -0
  5. package/debug/Electrks_Feminine_Challenge.brs +0 -0
  6. package/debug/a5bricks.brs +0 -0
  7. package/debug/a5p2.2.brs +0 -0
  8. package/debug/a5p2.brs +0 -0
  9. package/debug/audio.brs +0 -0
  10. package/debug/brick a5.brs +0 -0
  11. package/debug/brick qa.brs +0 -0
  12. package/debug/brsv10.brs +0 -0
  13. package/debug/brsv10brick.brs +0 -0
  14. package/debug/bruteforce.js +254 -0
  15. package/debug/clone.html +36 -0
  16. package/debug/ctf_tileset.brs +0 -0
  17. package/debug/ctf_tileset_5.brs +0 -0
  18. package/debug/evil.brs +0 -0
  19. package/{src/write.js → debug/evilwrite.js} +218 -31
  20. package/debug/foo.txt +3080 -0
  21. package/debug/kenko_big.brs +0 -0
  22. package/debug/light.brs +0 -0
  23. package/debug/out.json +105 -0
  24. package/debug/read.js +30 -0
  25. package/debug/readSpeed.js +32 -0
  26. package/debug/readTest.js +45 -0
  27. package/debug/temp.brs +0 -0
  28. package/debug/western.brs +0 -0
  29. package/dist/dist.js +1 -1
  30. package/dist/dist.node.js +1 -1
  31. package/dist/dist.web.js +1 -1
  32. package/dist/src/constants.d.ts +6 -0
  33. package/dist/src/constants.d.ts.map +1 -0
  34. package/dist/src/index.d.ts +23 -0
  35. package/dist/src/index.d.ts.map +1 -0
  36. package/dist/src/read.d.ts +3 -0
  37. package/dist/src/read.d.ts.map +1 -0
  38. package/dist/src/read.v1.d.ts +3 -0
  39. package/dist/src/read.v1.d.ts.map +1 -0
  40. package/dist/src/read.v10.d.ts +3 -0
  41. package/dist/src/read.v10.d.ts.map +1 -0
  42. package/dist/src/read.v2.d.ts +3 -0
  43. package/dist/src/read.v2.d.ts.map +1 -0
  44. package/dist/src/read.v3.d.ts +3 -0
  45. package/dist/src/read.v3.d.ts.map +1 -0
  46. package/dist/src/read.v4.d.ts +3 -0
  47. package/dist/src/read.v4.d.ts.map +1 -0
  48. package/dist/src/read.v8.d.ts +3 -0
  49. package/dist/src/read.v8.d.ts.map +1 -0
  50. package/dist/src/read.v9.d.ts +3 -0
  51. package/dist/src/read.v9.d.ts.map +1 -0
  52. package/dist/src/types.d.ts +170 -0
  53. package/dist/src/types.d.ts.map +1 -0
  54. package/dist/src/utils.d.ts +87 -0
  55. package/dist/src/utils.d.ts.map +1 -0
  56. package/dist/src/uuid.d.ts +4 -0
  57. package/dist/src/uuid.d.ts.map +1 -0
  58. package/dist/src/write.d.ts +3 -0
  59. package/dist/src/write.d.ts.map +1 -0
  60. package/examples/ATCFort.brs +0 -0
  61. package/examples/read_example.html +34 -0
  62. package/examples/read_example.js +21 -0
  63. package/examples/write_example.js +25 -0
  64. package/examples/write_planet.html +144 -0
  65. package/examples/write_simplex.html +82 -0
  66. package/examples/write_wedge_sphere.html +173 -0
  67. package/package.json +17 -14
  68. package/src/constants.ts +6 -0
  69. package/src/{index.js → index.ts} +13 -1
  70. package/src/read.ts +57 -0
  71. package/src/read.v1.ts +99 -0
  72. package/src/read.v10.ts +185 -0
  73. package/src/read.v2.ts +102 -0
  74. package/src/read.v3.ts +111 -0
  75. package/src/read.v4.ts +112 -0
  76. package/src/read.v8.ts +172 -0
  77. package/src/read.v9.ts +181 -0
  78. package/src/types.ts +240 -0
  79. package/src/{utils.js → utils.ts} +241 -181
  80. package/src/{uuid.js → uuid.ts} +9 -5
  81. package/src/write.ts +209 -0
  82. package/test/lib.test.js +31 -23
  83. package/test/utils.test.js +50 -15
  84. package/tsconfig.json +22 -0
  85. package/webpack.config.js +51 -30
  86. package/src/constants.js +0 -4
  87. package/src/read.js +0 -206
@@ -0,0 +1,173 @@
1
+ <!DOCTYPE html>
2
+ <script src="https://cdn.jsdelivr.net/gh/meshiest/brs-js/dist/dist.js"></script>
3
+ <script src="https://cdn.jsdelivr.net/gh/josephg/noisejs/perlin.js"></script>
4
+
5
+ <a id="anchor" download="generated.brs">Download</a>
6
+
7
+ <script>
8
+
9
+ console.time('Generate');
10
+
11
+ // Generate fake UUID for terrain owner so you can /clearbricks terrain
12
+ const author = {
13
+ id: 'a8033bee-6c37-4118-b4a6-cecc1d966132',
14
+ name: 'terrain',
15
+ };
16
+
17
+ noise.seed(Math.random());
18
+
19
+ const size = 200;
20
+ const brickSize = 40;
21
+
22
+ // Build an array for the entire volume made of cubes
23
+ const locations = Array.from({length: size ** 3})
24
+ .map((_, i) => {
25
+ let obj = {
26
+ x: i % size,
27
+ y: Math.floor(i / size) % size,
28
+ z: Math.floor(i / size / size) % size,
29
+ width: 1,
30
+ depth: 1,
31
+ height: 1,
32
+ kind: 0,
33
+ material: 2,
34
+ rotation: 0,
35
+ };
36
+ obj.v = (
37
+ Math.max( // caves
38
+ Math.abs(noise.perlin3(obj.x / 30, obj.y / 30, obj.z / 30)),
39
+ Math.abs(noise.perlin3(obj.x / 30 + 9000, obj.y / 30 - 2000, obj.z / 30 + 4000))
40
+ ) > 0.15 &&
41
+ Math.hypot(obj.x - size/2, obj.y - size/2, obj.z - size/2) < size/2 // sphere mask
42
+ ) ? 1 : 0;
43
+ return obj;
44
+ });
45
+
46
+ // Get brick at position
47
+ const at2 = (x, y, z) =>
48
+ x < 0 || y < 0 || z < 0 || x >= size || y >= size || z >= size
49
+ ? null
50
+ : locations[x + y * size + z * size * size];
51
+
52
+ // get if brick exists at position
53
+ const at = (x, y, z) => {
54
+ const brick = at2(x, y, z);
55
+ return (brick && brick.v) ? 1 : 0;
56
+ };
57
+
58
+
59
+ // Get a bit mask of the neighbors in West, North, East, South order
60
+ const getNeighbors = (x, y, z) => (at(x - 1, y, z) << 3) | (at(x, y - 1, z) << 2) | (at(x + 1, y, z) << 1) | (at(x, y + 1, z));
61
+
62
+ // convert corners to wedges
63
+ const wedgify = () => locations
64
+ .filter(obj => obj.v)
65
+ .forEach(obj => {
66
+ const {x, y, z} = obj;
67
+ const neighbors = getNeighbors(x, y, z);
68
+
69
+ // delta x/y variables are for increasing width of wedges
70
+ let dx = 0;
71
+ let dy = 0;
72
+
73
+ // handle each of the possible rotations on the Z axis
74
+ if (neighbors === 0b0011) {
75
+ obj.kind = 1;
76
+ obj.rotation = 2;
77
+ }
78
+ else if (neighbors === 0b1100) {
79
+ obj.kind = 1;
80
+ obj.rotation = 0;
81
+ }
82
+ else if (neighbors === 0b1001) {
83
+ obj.kind = 1;
84
+ obj.rotation = 3;
85
+ }
86
+ else if (neighbors === 0b0110) {
87
+ obj.kind = 1;
88
+ obj.rotation = 1;
89
+ }
90
+
91
+ // re-center wedges that got resized
92
+ if(obj.kind === 1) {
93
+ obj.width += Math.abs(dx);
94
+ obj.x = dx / 2 + x;
95
+ obj.depth += Math.abs(dy);
96
+ obj.y = dy / 2 + y;
97
+ }
98
+ });
99
+
100
+ wedgify();
101
+
102
+ // Hollow-out the middle by removing cubes surrounding
103
+ locations
104
+ .filter(obj => obj.v)
105
+ .forEach(obj => {
106
+ const {x, y, z} = obj;
107
+ const neighbors = [ // all neighboring cubes
108
+ at2(x, y, z + 1),
109
+ at2(x, y, z - 1),
110
+ at2(x - 1, y, z),
111
+ at2(x + 1, y, z),
112
+ at2(x, y - 1, z),
113
+ at2(x, y + 1, z),
114
+ ];
115
+
116
+ // check if surrounded by bricks that exist and are not wedges
117
+ if(neighbors.every(n => n && n.v !== 0 && n.kind === 0)) {
118
+ obj.v = 2;
119
+ }
120
+ });
121
+
122
+ // Uncomment below if you want wedges on the inside (creates holes in some places)
123
+
124
+ /*
125
+ locations.filter(obj => obj.v === 2).forEach(obj => obj.v = 0);
126
+ wedgify();
127
+ */
128
+
129
+ const save = {
130
+ author: author,
131
+ description: 'Cave Sphere',
132
+ map: 'JavaScript',
133
+ brick_owners: [author],
134
+ brick_assets: [
135
+ 'PB_DefaultBrick',
136
+ 'PB_DefaultSideWedge',
137
+ ],
138
+ materials: [
139
+ 'BMC_Ghost',
140
+ 'BMC_Ghost_Fail',
141
+ 'BMC_Plastic',
142
+ 'BMC_Glow',
143
+ 'BMC_Metallic',
144
+ 'BMC_Hologram',
145
+ ],
146
+ bricks: locations
147
+ .filter(o => o.v === 1)
148
+ .map(({x, y, z, kind, rotation, material, width, height, depth}) => ({
149
+ // give multiple dimensions in case we resize wedges
150
+ size: [brickSize * width, brickSize * depth, brickSize * height],
151
+ asset_name_index: kind,
152
+ material_index: material,
153
+ rotation,
154
+ // 3d color blobs that are kinda grey-ish
155
+ color: [
156
+ Math.floor(noise.perlin3(x / 50, y / 50, z / 50) * 80 + 100),
157
+ Math.floor(noise.perlin3(x / 50 - 3000, y / 50 - 3000, z / 50 + 200) * 80 + 100),
158
+ Math.floor(noise.perlin3(x / 50 + 2000, y / 50 + 2000, z / 50 - 2000) * 80 + 100),
159
+ 255,
160
+ ],
161
+ position: [
162
+ x * brickSize * 2,
163
+ y * brickSize * 2,
164
+ z * brickSize * 2,
165
+ ],
166
+ })),
167
+ };
168
+
169
+ const blob = new Blob([new Uint8Array(BRS.write(save))]);
170
+ console.timeEnd('Generate');
171
+ anchor.href = URL.createObjectURL(blob);
172
+
173
+ </script>
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "brs-js",
3
- "version": "1.4.1",
3
+ "version": "2.0.2",
4
4
  "description": "Brickadia Save Read/Writing",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "build": "npx webpack",
8
- "watch": "npx webpack -w",
9
- "dist": "npx webpack -p",
7
+ "build": "npx webpack --progress",
8
+ "watch": "npx webpack -w --progress",
9
+ "dist": "NODE_ENV=production npx webpack",
10
10
  "test": "npx jest"
11
11
  },
12
12
  "repository": {
@@ -27,17 +27,20 @@
27
27
  },
28
28
  "homepage": "https://github.com/meshiest/brs-js#readme",
29
29
  "dependencies": {
30
- "detect-node": "^2.0.4",
31
- "lodash": "^4.17.19",
32
- "pako": "^1.0.10",
33
- "punycode": "^2.1.1"
30
+ "detect-node": "^2.0.4"
34
31
  },
32
+ "types": "./dist/src/index.d.ts",
35
33
  "devDependencies": {
36
- "@babel/core": "^7.6.0",
37
- "@babel/preset-env": "^7.6.0",
34
+ "@babel/core": "^7.14.6",
35
+ "@babel/preset-env": "^7.14.7",
36
+ "@types/pako": "^1.0.2",
38
37
  "babel-loader": "^8.0.6",
39
- "jest": "^26.1.0",
40
- "webpack": "^4.44.0",
41
- "webpack-cli": "^3.3.12"
38
+ "jest": "^27.0.6",
39
+ "pako": "^1.0.10",
40
+ "prettier": "^2.3.2",
41
+ "ts-loader": "^9.2.3",
42
+ "typescript": "^4.3.5",
43
+ "webpack": "^5.44.0",
44
+ "webpack-cli": "^4.7.2"
42
45
  }
43
- }
46
+ }
@@ -0,0 +1,6 @@
1
+ import { Uuid } from './types';
2
+
3
+ export const MAGIC = new Uint8Array([66, 82, 83]); // BRS
4
+ export const LATEST_VERSION = 10;
5
+ export const MAX_INT = ~(1 << 31);
6
+ export const DEFAULT_UUID: Uuid = '00000000-0000-0000-0000-000000000000';
@@ -8,5 +8,17 @@ const brs = { read, write, utils, constants };
8
8
  export { read, write, utils, constants };
9
9
  export default brs;
10
10
 
11
- if (typeof window !== 'undefined')
11
+ declare global {
12
+ interface Window {
13
+ BRS: {
14
+ read: typeof read;
15
+ write: typeof write;
16
+ utils: typeof utils;
17
+ constants: typeof constants;
18
+ };
19
+ }
20
+ }
21
+
22
+ if (typeof window !== 'undefined') {
12
23
  window.BRS = brs;
24
+ }
package/src/read.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { MAGIC } from './constants';
2
+ import readBrsV1 from './read.v1';
3
+ import readBrsV10 from './read.v10';
4
+ import readBrsV2 from './read.v2';
5
+ import readBrsV3 from './read.v3';
6
+ import readBrsV4 from './read.v4';
7
+ import readBrsV8 from './read.v8';
8
+ import readBrsV9 from './read.v9';
9
+ import { BRSBytes, ReadOptions, ReadSaveObject } from './types';
10
+ import { read } from './utils';
11
+
12
+ // Reads in a byte array to build a brs object
13
+ export default function readBrs(
14
+ rawBytes: Uint8Array,
15
+ options: ReadOptions = {}
16
+ ): ReadSaveObject {
17
+ if (typeof options !== 'object') throw new Error('Invalid options');
18
+
19
+ // default enable brick reading
20
+ if (typeof options.bricks !== 'boolean') options.bricks = true;
21
+
22
+ // default disable preview (a5 only)
23
+ if (typeof options.preview !== 'boolean') options.preview = false;
24
+
25
+ // Determine if the first 3 bytes are equal to the Brickadia save magic bytes
26
+ if (
27
+ rawBytes[0] !== MAGIC[0] ||
28
+ rawBytes[1] !== MAGIC[1] ||
29
+ rawBytes[2] !== MAGIC[2]
30
+ ) {
31
+ throw new Error('Invalid starting bytes');
32
+ }
33
+
34
+ const brsData = rawBytes as BRSBytes;
35
+ brsData.brsOffset = 3;
36
+
37
+ // Determine if the file version supported
38
+ const version = read.u16(brsData);
39
+ switch (version) {
40
+ case 1:
41
+ return readBrsV1(brsData, options);
42
+ case 2:
43
+ return readBrsV2(brsData, options);
44
+ case 3:
45
+ return readBrsV3(brsData, options);
46
+ case 4:
47
+ return readBrsV4(brsData, options);
48
+ case 8:
49
+ return readBrsV8(brsData, options);
50
+ case 9:
51
+ return readBrsV9(brsData, options);
52
+ case 10:
53
+ return readBrsV10(brsData, options);
54
+ default:
55
+ throw new Error('Unsupported version ' + version);
56
+ }
57
+ }
package/src/read.v1.ts ADDED
@@ -0,0 +1,99 @@
1
+ import {
2
+ BRSBytes,
3
+ ReadOptions,
4
+ BrsV1,
5
+ UnrealColor,
6
+ BrickV1,
7
+ Vector,
8
+ } from './types';
9
+ import { bgra, read } from './utils';
10
+
11
+ // Reads in a byte array to build a brs object
12
+ export default function readBrsV1(
13
+ brsData: BRSBytes,
14
+ options: ReadOptions = {}
15
+ ): BrsV1 {
16
+ // Read in Headers
17
+ const header1Data = read.compressed(brsData);
18
+ const header2Data = read.compressed(brsData);
19
+
20
+ const map = read.string(header1Data);
21
+ const author_name = read.string(header1Data);
22
+ const description = read.string(header1Data);
23
+
24
+ const author_id = read.uuid(header1Data);
25
+
26
+ const brick_count = read.i32(header1Data);
27
+
28
+ const mods = read.array(header2Data, read.string);
29
+ const brick_assets = read.array(header2Data, read.string);
30
+ const colors = read.array(
31
+ header2Data,
32
+ data => bgra(Array.from(read.bytes(data, 4))) as UnrealColor
33
+ );
34
+
35
+ // Read in bricks
36
+ let bricks: BrickV1[] = [];
37
+
38
+ const numAssets = Math.max(brick_assets.length, 2);
39
+
40
+ if (options.bricks) {
41
+ bricks = Array(brick_count);
42
+ const brickData = read.compressed(brsData);
43
+ const brickBits = read.bits(brickData);
44
+
45
+ // Brick reader
46
+ for (let i = 0; !brickBits.empty() && i < brick_count; i++) {
47
+ brickBits.align();
48
+ const asset_name_index = brickBits.int(numAssets);
49
+ const size: Vector = brickBits.bit()
50
+ ? [
51
+ brickBits.uint_packed(),
52
+ brickBits.uint_packed(),
53
+ brickBits.uint_packed(),
54
+ ]
55
+ : [0, 0, 0];
56
+ const position: Vector = [
57
+ brickBits.int_packed(),
58
+ brickBits.int_packed(),
59
+ brickBits.int_packed(),
60
+ ];
61
+
62
+ const orientation = brickBits.int(24);
63
+ const direction = (orientation >> 2) % 6;
64
+ const rotation = orientation & 3;
65
+ const collision = brickBits.bit();
66
+ const visibility = brickBits.bit();
67
+
68
+ const color = brickBits.bit()
69
+ ? bgra(brickBits.bytesArr(4) as [number, number, number, number])
70
+ : brickBits.int(colors.length);
71
+
72
+ bricks[i] = {
73
+ asset_name_index,
74
+ size,
75
+ collision,
76
+ position,
77
+ direction,
78
+ rotation,
79
+ visibility,
80
+ color,
81
+ };
82
+ }
83
+ }
84
+
85
+ return {
86
+ version: 1,
87
+ map,
88
+ description,
89
+ author: {
90
+ id: author_id,
91
+ name: author_name,
92
+ },
93
+ mods,
94
+ brick_assets,
95
+ colors,
96
+ bricks,
97
+ brick_count,
98
+ };
99
+ }
@@ -0,0 +1,185 @@
1
+ import {
2
+ AppliedComponent,
3
+ BrickV10,
4
+ BRSBytes,
5
+ BrsV10,
6
+ Collision,
7
+ ReadOptions,
8
+ UnrealColor,
9
+ User,
10
+ Vector,
11
+ } from './types';
12
+ import { bgra, read } from './utils';
13
+
14
+ // Reads in a byte array to build a brs object
15
+ export default function readBrsV10(
16
+ brsData: BRSBytes,
17
+ options: ReadOptions = {}
18
+ ): BrsV10 {
19
+ // game version is included in saves >= v8
20
+ const game_version = read.i32(brsData);
21
+
22
+ // Read in Headers
23
+ const header1Data = read.compressed(brsData);
24
+ const header2Data = read.compressed(brsData);
25
+
26
+ const map = read.string(header1Data);
27
+ const author_name = read.string(header1Data);
28
+ const description = read.string(header1Data);
29
+ const author_id = read.uuid(header1Data);
30
+ const host: User = {
31
+ name: read.string(header1Data),
32
+ id: read.uuid(header1Data),
33
+ };
34
+ const save_time = read.bytes(header1Data, 8);
35
+
36
+ const brick_count = read.i32(header1Data);
37
+
38
+ const mods = read.array(header2Data, read.string);
39
+ const brick_assets = read.array(header2Data, read.string);
40
+ const colors = read.array(
41
+ header2Data,
42
+ data => bgra(Array.from(read.bytes(data, 4))) as UnrealColor
43
+ );
44
+ const materials = read.array(header2Data, read.string);
45
+
46
+ const brick_owners = read.array(header2Data, data => ({
47
+ id: read.uuid(data),
48
+ name: read.string(data),
49
+ bricks: read.i32(data),
50
+ }));
51
+
52
+ const physical_materials = read.array(header2Data, read.string);
53
+
54
+ // check for preview byte
55
+ let preview = null;
56
+ if (read.bytes(brsData, 1)[0]) {
57
+ const len = read.i32(brsData);
58
+ if (options.preview) {
59
+ preview = read.bytes(brsData, len);
60
+ } else {
61
+ brsData.brsOffset += len;
62
+ }
63
+ }
64
+
65
+ // Read in bricks
66
+ let bricks: BrickV10[] = [];
67
+ const components: BrsV10['components'] = {};
68
+
69
+ const numPhysMats = Math.max(physical_materials.length, 2);
70
+ const numMats = Math.max(materials.length, 2);
71
+ const numAssets = Math.max(brick_assets.length, 2);
72
+
73
+ if (options.bricks) {
74
+ bricks = Array(brick_count);
75
+ const brickData = read.compressed(brsData);
76
+ const brickBits = read.bits(brickData);
77
+
78
+ // Brick reader
79
+ for (let i = 0; !brickBits.empty() && i < brick_count; i++) {
80
+ brickBits.align();
81
+ const asset_name_index = brickBits.int(numAssets);
82
+ const size: Vector = brickBits.bit()
83
+ ? [
84
+ brickBits.uint_packed(),
85
+ brickBits.uint_packed(),
86
+ brickBits.uint_packed(),
87
+ ]
88
+ : [0, 0, 0];
89
+ const position: Vector = [
90
+ brickBits.int_packed(),
91
+ brickBits.int_packed(),
92
+ brickBits.int_packed(),
93
+ ];
94
+
95
+ const orientation = brickBits.int(24);
96
+ const direction = (orientation >> 2) % 6;
97
+ const rotation = orientation & 3;
98
+ const collision: Collision = {
99
+ player: brickBits.bit(),
100
+ weapon: brickBits.bit(),
101
+ interaction: brickBits.bit(),
102
+ tool: brickBits.bit(),
103
+ };
104
+ const visibility = brickBits.bit();
105
+ const material_index = brickBits.int(numMats);
106
+ const physical_index = brickBits.int(numPhysMats);
107
+ const material_intensity = brickBits.int(11);
108
+ const color = brickBits.bit()
109
+ ? (brickBits.bytesArr(3) as [number, number, number])
110
+ : brickBits.int(colors.length);
111
+
112
+ const owner_index = brickBits.uint_packed();
113
+
114
+ bricks[i] = {
115
+ asset_name_index,
116
+ size,
117
+ position,
118
+ direction,
119
+ rotation,
120
+ collision: collision as Collision,
121
+ visibility,
122
+ material_index,
123
+ physical_index,
124
+ material_intensity,
125
+ color,
126
+ owner_index,
127
+ components: {},
128
+ };
129
+ }
130
+
131
+ const componentData = read.compressed(brsData);
132
+ const numBricks = Math.max(bricks.length, 2);
133
+
134
+ read.each(componentData, data => {
135
+ // read component name
136
+ const name = read.string(data);
137
+
138
+ // read component body
139
+ const bits = read.bits(read.bytes(data, read.i32(data)));
140
+
141
+ const version = read.i32(bits.bytes(4));
142
+ // list of bricks
143
+ const brick_indices = bits.array(() => bits.int(numBricks));
144
+
145
+ // list of name, type properties
146
+ const properties = bits.array(() => [bits.string(), bits.string()]);
147
+
148
+ // read components for each brick
149
+ for (const i of brick_indices) {
150
+ const props: AppliedComponent = {};
151
+ for (const [name, type] of properties) props[name] = bits.unreal(type);
152
+ bricks[i].components[name] = props;
153
+ }
154
+
155
+ components[name] = {
156
+ version,
157
+ brick_indices,
158
+ properties: Object.fromEntries(properties),
159
+ };
160
+ });
161
+ }
162
+
163
+ return {
164
+ version: 10,
165
+ game_version,
166
+ map,
167
+ description,
168
+ author: {
169
+ id: author_id,
170
+ name: author_name,
171
+ },
172
+ host,
173
+ mods,
174
+ brick_assets,
175
+ colors,
176
+ materials,
177
+ brick_owners,
178
+ physical_materials,
179
+ preview,
180
+ bricks,
181
+ brick_count,
182
+ save_time,
183
+ components,
184
+ };
185
+ }
package/src/read.v2.ts ADDED
@@ -0,0 +1,102 @@
1
+ import {
2
+ BRSBytes,
3
+ ReadOptions,
4
+ BrsV2,
5
+ UnrealColor,
6
+ BrickV2,
7
+ Vector,
8
+ } from './types';
9
+ import { bgra, read } from './utils';
10
+
11
+ // Reads in a byte array to build a brs object
12
+ export default function readBrsV2(
13
+ brsData: BRSBytes,
14
+ options: ReadOptions = {}
15
+ ): BrsV2 {
16
+ // Read in Headers
17
+ const header1Data = read.compressed(brsData);
18
+ const header2Data = read.compressed(brsData);
19
+
20
+ const map = read.string(header1Data);
21
+ const author_name = read.string(header1Data);
22
+ const description = read.string(header1Data);
23
+ const author_id = read.uuid(header1Data);
24
+
25
+ const brick_count = read.i32(header1Data);
26
+
27
+ const mods = read.array(header2Data, read.string);
28
+ const brick_assets = read.array(header2Data, read.string);
29
+ const colors = read.array(
30
+ header2Data,
31
+ data => bgra(Array.from(read.bytes(data, 4))) as UnrealColor
32
+ );
33
+ const materials = read.array(header2Data, read.string);
34
+
35
+ // Read in bricks
36
+ let bricks: BrickV2[] = [];
37
+
38
+ const numAssets = Math.max(brick_assets.length, 2);
39
+
40
+ if (options.bricks) {
41
+ bricks = Array(brick_count);
42
+ const brickData = read.compressed(brsData);
43
+ const brickBits = read.bits(brickData);
44
+
45
+ // Brick reader
46
+ for (let i = 0; !brickBits.empty() && i < brick_count; i++) {
47
+ brickBits.align();
48
+ const asset_name_index = brickBits.int(numAssets);
49
+ const size: Vector = brickBits.bit()
50
+ ? [
51
+ brickBits.uint_packed(),
52
+ brickBits.uint_packed(),
53
+ brickBits.uint_packed(),
54
+ ]
55
+ : [0, 0, 0];
56
+ const position: Vector = [
57
+ brickBits.int_packed(),
58
+ brickBits.int_packed(),
59
+ brickBits.int_packed(),
60
+ ];
61
+
62
+ const orientation = brickBits.int(24);
63
+ const direction = (orientation >> 2) % 6;
64
+ const rotation = orientation & 3;
65
+ const collision = brickBits.bit();
66
+ const visibility = brickBits.bit();
67
+ const material_index = brickBits.bit() ? brickBits.uint_packed() : 1;
68
+
69
+ const color = brickBits.bit()
70
+ ? bgra(brickBits.bytesArr(4) as [number, number, number, number])
71
+ : brickBits.int(colors.length);
72
+
73
+ bricks[i] = {
74
+ asset_name_index,
75
+ size,
76
+ position,
77
+ direction,
78
+ rotation,
79
+ collision,
80
+ visibility,
81
+ material_index,
82
+ color,
83
+ };
84
+ }
85
+ }
86
+
87
+ return {
88
+ version: 2,
89
+ map,
90
+ description,
91
+ author: {
92
+ id: author_id,
93
+ name: author_name,
94
+ },
95
+ mods,
96
+ brick_assets,
97
+ colors,
98
+ materials,
99
+ bricks,
100
+ brick_count,
101
+ };
102
+ }