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
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "arrowParens": "avoid",
3
+ "endOfLine": "lf",
4
+ "printWidth": 80,
5
+ "semi": true,
6
+ "singleQuote": true
7
+ }
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Isaac Hirschfeld
3
+ Copyright (c) 2021 Isaac Hirschfeld
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Read and write Brickadia save files (.brs)
4
4
 
5
- Currently supports save versions <= 8
5
+ Currently supports save versions <= 10
6
6
 
7
7
  **Warning:** __Unreal Engine uses numbers potentially larger than Javascript can handle.__
8
8
 
@@ -116,7 +116,7 @@ Unsigned ints, while unlikely, may overflow.
116
116
  | field | type | default | optional | description |
117
117
  |-----------------------------|--------|--------------------------------------|----------|----------------------------------|
118
118
  | version | short | Latest Save Version | auto | Save file version |
119
- | gameVersion | int | Game Version | &#9745; | Saving version of the game |
119
+ | game_version | int | Game Version | &#9745; | Saving version of the game |
120
120
  | map | string | 'Unknown' | &#9745; | Map where the save was generated |
121
121
  | author.id | uuid | 00000000-0000-0000-0000-000000000000 | &#9745; | Save author UUID |
122
122
  | author.name | string | 'Unknown' | &#9745; | Save author name |
Binary file
Binary file
Binary file
package/debug/a5p2.brs ADDED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,254 @@
1
+ const fs = require('fs');
2
+
3
+ const {utils: {read, isEqual}, constants: {MAGIC}} = require('../dist/dist.node.js');
4
+
5
+ // arg parsing
6
+ const file = process.argv.find(f => f.endsWith('brs'));
7
+ if (!file) {
8
+ console.error('missing save argument');
9
+ process.exit(1);
10
+ }
11
+ if (!fs.existsSync(file)) {
12
+ console.error('file does not exist');
13
+ process.exit(1);
14
+ }
15
+
16
+ // read the file
17
+ const data = Array.from(fs.readFileSync(file));
18
+ const copyData = () => data.slice();
19
+
20
+ const bgra = ([b, g, r, a]) => [r, g, b, a];
21
+ const PNG_MAGIC = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82];
22
+ let quiet = false;
23
+
24
+ function tryRead(data, type, fn, results) {
25
+ try {
26
+ if (typeof type === 'function')
27
+ return type(data, results);
28
+
29
+ switch(type) {
30
+ case 'magic': return isEqual(data.splice(0, MAGIC.length), MAGIC);
31
+ case 'pngmagic': return isEqual(data.splice(0, PNG_MAGIC.length), PNG_MAGIC);
32
+ case 'compressed': return read.compressed(data);
33
+ case 'compressed.array': return read.array(read.compressed(data), data => fn(data, results));
34
+ case 'string': return read.string(data);
35
+ case 'uuid': return read.uuid(data);
36
+ case 'byte': return data.splice(0, 1)[0];
37
+ case 'u16': return read.u16(data);
38
+ case 'i32': return read.i32(data);
39
+ case 'time': return data.splice(0, 8);
40
+ case 'word': return data.splice(0, 4);
41
+ case 'bits': return read.bits(data);
42
+ case 'array': return read.array(data, data => fn(data, results));
43
+ case 'rest': return data.splice(0, data.length);
44
+ case 'bricks':
45
+ const brickData = read.compressed(data);
46
+ // console.log(brickData.map(d => d.toString(2).padStart(8, '0')).join(''));
47
+ const brickBits = read.bits(brickData);
48
+ const bricks = [];
49
+
50
+ try {
51
+ while(!brickBits.empty() && bricks.length < results.header1.brickCount) {
52
+ brickBits.align();
53
+ const start = brickBits.pos;
54
+ const brick = fn(brickBits, results);
55
+ brick.bitsize = (brickBits.pos - start);
56
+ bricks.push(brick);
57
+ }
58
+ } catch (e) {
59
+ console.error(e)
60
+ return bricks;
61
+ }
62
+
63
+ return bricks;
64
+ }
65
+ } catch (e) {
66
+ console.log(e);
67
+ return ';;badvalue;;';
68
+ }
69
+ }
70
+
71
+ function bruteforce(data, steps, indent=0) {
72
+ let i = 0;
73
+ const results = {};
74
+ for (let step of steps) {
75
+ i ++;
76
+
77
+ // convert string and arrays into objects
78
+ if (typeof step === 'string')
79
+ step = {name: step, bin: false, type: step};
80
+ if (typeof step === 'object' && typeof step.length === 'number')
81
+ step = {name: 'scan', attempt: step};
82
+
83
+ // check if ther's any data left
84
+ if (data.length === 0) {
85
+ if(!quiet) console.log(''.padStart(indent * 2, ' '), i, ')', step.name || '?', '[no bytes left]');
86
+ break;
87
+ }
88
+
89
+ let type = step.type;
90
+
91
+ // if this is a conditional step, assign the next steps based on this one
92
+ if (step.test) {
93
+ const test = step.test(tryRead(data, step.type));
94
+ if(!quiet) console.log(''.padStart(indent * 2, ' '), i, '-', step.name || '?', 'test', test);
95
+ step.contains = test ? step.yes : step.no;
96
+ step.type = 'rest';
97
+ } else if (!type) {
98
+ // scan for a matching type
99
+ type = step.attempt.find(t => tryRead(data.slice(), typeof t === 'object' ? t.name : t, t.fn) !== ';;badvalue;;');
100
+ }
101
+
102
+
103
+ const val = step.test ? 'test' : tryRead(data, type, step.fn, results);
104
+ if(!quiet) console.log(''.padStart(indent * 2, '.'), i, ')', step.name || '?', '=', step.bin ? val.length + ' bytes' : val);
105
+ results[step.name] = step.contains
106
+ ? bruteforce(step.test ? data : val, step.contains, indent+1, results)
107
+ : val;
108
+ }
109
+
110
+ return results;
111
+ }
112
+
113
+ const go = (before, after) => bruteforce(copyData(), [
114
+ {name: 'BRS', type: 'magic'},
115
+ {name: 'version', type: 'u16'},
116
+ {name: 'game version', type: 'i32'},
117
+ {
118
+ name: 'header1', type: 'compressed', bin: true,
119
+ contains: [
120
+ {name: 'map', type: 'string'},
121
+ {name: 'saverName', type: 'string'},
122
+ {name: 'description', type: 'string'},
123
+ {name: 'saverId', type: 'uuid'},
124
+ {name: 'hostName', type: 'string'},
125
+ {name: 'hostId', type: 'uuid'},
126
+ {name: 'saveDate', type: 'time'},
127
+ {name: 'brickCount', type: 'i32'},
128
+ ],
129
+ },
130
+ {
131
+ name: 'header2', type: 'compressed', bin: true,
132
+ contains: [
133
+ {name: 'mods', type: 'array', fn: read.string},
134
+ {name: 'assets', type: 'array', fn: read.string},
135
+ {name: 'colors', type: 'array', fn: data => bgra(data.splice(0, 4))},
136
+ {name: 'materials', type: 'array', fn: read.string},
137
+ {name: 'owners', type: 'array', fn: data => ({
138
+ id: read.uuid(data),
139
+ name: read.string(data),
140
+ count: read.i32(data)
141
+ })},
142
+ ],
143
+ },
144
+ {
145
+ name: 'preview',
146
+ test: v => v, type: 'byte',
147
+ yes: [
148
+ {name: 'data', type: data => {
149
+ const len = read.i32(data);
150
+ const png = data.splice(0, len);
151
+ return len + ' bytes';
152
+ }},
153
+ ],
154
+ no: [],
155
+ },
156
+ {
157
+ name: 'bricks',
158
+ type: 'bricks',
159
+ fn: (bits, parent) => ({
160
+ asset_index: bits.int(Math.max(parent.header2.assets.length, 2)),
161
+ size: bits.bit() ? [bits.uint_packed(), bits.uint_packed(), bits.uint_packed()] : [0, 0, 0],
162
+ position: [bits.int_packed(), bits.int_packed(), bits.int_packed()],
163
+ ...(orientation => ({
164
+ direction: (orientation >> 2) % 6,
165
+ rotation: orientation & 3,
166
+ }))(bits.int(24)),
167
+ collision: bits.bit(),
168
+ visibility: bits.bit(),
169
+ material_index: bits.bit() ? bits.uint_packed() : 1,
170
+ // material_index: bits.int(Math.max(parent.header2.materials.length, 2)),
171
+ color: bits.bit() ? bgra(bits.bytes(4)) : bits.int(parent.header2.colors.length),
172
+ owner_index: bits.uint_packed(true),
173
+ }),
174
+ },
175
+ {
176
+ name: 'components', type: 'compressed.array', fn: (data, parent) => {
177
+ const name = read.string(data);
178
+ const bits = read.bits(data.splice(0, read.i32(data)));
179
+ const version = read.i32(bits.bytes(4));
180
+ const brick_indices = bits.array(() => bits.int(Math.max(parent.header1.brickCount, 2)));
181
+ const properties = bits.array(() => [bits.string(), bits.string()]);
182
+
183
+ const bricks = brick_indices.map(i => ({
184
+ index: i,
185
+ props: Object.fromEntries(properties.map(([name, type]) => {
186
+ let val;
187
+ switch(type) {
188
+ case 'Class':
189
+ val = bits.string();
190
+ break;
191
+ case 'Boolean':
192
+ val = !!read.i32(bits.bytes(4));
193
+ break;
194
+ case 'Float':
195
+ val = bits.float();
196
+ break;
197
+ case 'Color':
198
+ val = bgra(bits.bytes(4));
199
+ break;
200
+ case 'Rotator':
201
+ val = [bits.float(), bits.float(), bits.float()];
202
+ break;
203
+ }
204
+ return [name, val];
205
+ })),
206
+ }));
207
+ return {
208
+ name,
209
+ version,
210
+ brick_indices,
211
+ properties: Object.fromEntries(properties),
212
+ bricks,
213
+ }
214
+ },
215
+ },
216
+ ]);
217
+
218
+ /*
219
+ for each component type used in the build, it will
220
+ write the component type name [done]
221
+ write the version [done]
222
+ write an array of brick indices to attach to [done]
223
+ write the data layout
224
+ write an array of property infos
225
+ write the name of the property
226
+ write the type of the property
227
+ write the size of the following data section
228
+ write the data
229
+ write each component instance
230
+ write each property
231
+
232
+ */
233
+
234
+ // console.log(go(1).bricks.bricks.slice(0, 2))
235
+
236
+ quiet = true;
237
+ for (let before = 0; before < 1; before++) {
238
+ for (let after = 0; after < 1; after++) {
239
+ const res = go(before, after);
240
+ // return;
241
+ // const colorOk = res.bricks.every(b => b.color === 0);
242
+ const bricksOk = res.bricks.length === res.header1.brickCount && res.bricks.every(b => b.owner_index <= res.header2.owners.length );
243
+ // const ownerOk = res.bricks.some(b => b.owner_index !== 1) && res.bricks.some(b => b.owner_index === 0);
244
+ // const materialOk = res.bricks.every(b => b.color === 0);
245
+ if (true) {
246
+ const bin = arr => arr.map(a => a?'1':'0').join('');
247
+ res.components.map(c => console.log(c, ...c.bricks))
248
+ console.log(
249
+ res,
250
+ {bricks: res.bricks.length, before, after},
251
+ );
252
+ }
253
+ }
254
+ }
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <script src="../dist/dist.js"></script>
3
+
4
+ <!-- Files uploaded will be -->
5
+ <input id="fileInput" type="file">
6
+
7
+ <a id="anchor" download="cloned.brs">Download</a>
8
+
9
+ <!-- This will be filled with the save object as JSON or the error message -->
10
+ <pre id="jsonElem"></pre>
11
+
12
+ <script>
13
+ fileInput.addEventListener('change', e => {
14
+ const file = e.target.files[0];
15
+ if (file) {
16
+ // Read the file into a byte array
17
+ file.arrayBuffer()
18
+ .then(buff => {
19
+ const save = BRS.read(buff);
20
+
21
+ // Log the save object
22
+ console.log(save);
23
+
24
+ // Render the save object as formatted JSON
25
+ jsonElem.innerText = '';
26
+
27
+ const blob = new Blob([new Uint8Array(BRS.write(save))]);
28
+ anchor.href = URL.createObjectURL(blob);
29
+ })
30
+ .catch(err => {
31
+ // Display the error
32
+ jsonElem.innerText = 'Error: ' + err.message;
33
+ });
34
+ }
35
+ });
36
+ </script>
Binary file
Binary file
package/debug/evil.brs ADDED
Binary file
@@ -1,23 +1,10 @@
1
- import { MAGIC, LATEST_VERSION, MAX_INT } from './constants';
2
- import { write, isEqual, concat } from './utils';
3
-
4
-
5
- // TODO: Validate input saves
6
- function validate(save) {
7
- if (typeof save !== 'object') {
8
- throw new Error('Expected save to be an object');
9
- }
10
-
11
- if (typeof save.bricks !== 'object' && save.bricks.length) {
12
- throw new Error('Expected save to have bricks field');
13
- }
14
-
15
- if (!save.bricks.every(b => typeof b.size === 'object' && typeof b.position === 'object'))
16
- throw new Error('Expected every brick to have size and position arrays')
17
- }
1
+ const {
2
+ utils: { write, isEqual, concat },
3
+ constants: { MAGIC, LATEST_VERSION, MAX_INT },
4
+ } = require('../dist/dist.node.js');
18
5
 
19
6
  // looks up a value in an object or returns a defualt value
20
- function get(obj, path='', def) {
7
+ function get(obj, path = '', def) {
21
8
  // Split the path up by .
22
9
  path = path.split('.').filter(p => p.length > 0);
23
10
 
@@ -31,10 +18,8 @@ function get(obj, path='', def) {
31
18
 
32
19
  const EMPTY_ARR = new Uint8Array([]);
33
20
 
34
- export default function writeBrs(save) {
35
- validate(save);
36
-
37
- if(save.bricks.length > MAX_INT) {
21
+ function writeSave(save) {
22
+ if (save.bricks.length > MAX_INT) {
38
23
  throw new Error('Brick count out of range');
39
24
  }
40
25
 
@@ -65,7 +50,7 @@ export default function writeBrs(save) {
65
50
  write.string(get(save, 'description', '')),
66
51
  write.uuid(get(save, 'author.id', '00000000-0000-0000-0000-000000000000')),
67
52
  version >= 8 ? concat(
68
- write.string(get(save ,'host.name', 'Unknown')),
53
+ write.string(get(save, 'host.name', 'Unknown')),
69
54
  write.uuid(get(save, 'host.id', '00000000-0000-0000-0000-000000000000')),
70
55
  ) : EMPTY_ARR,
71
56
  get(save, 'save_time', new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])),
@@ -78,7 +63,7 @@ export default function writeBrs(save) {
78
63
  write.array(get(save, 'brick_assets', ['PB_DefaultBrick']), write.string),
79
64
  write.array(get(save, 'colors', EMPTY_ARR), rgba),
80
65
  write.array(get(save, 'materials', ['BMC_Plastic']), write.string),
81
- write.array(get(save, 'brick_owners', [{}]), ({ id='00000000-0000-0000-0000-000000000000', name='Unknown', bricks=0 }={}) => concat(
66
+ write.array(get(save, 'brick_owners', [{}]), ({ id = '00000000-0000-0000-0000-000000000000', name = 'Unknown', bricks = 0 } = {}) => concat(
82
67
  write.uuid(id),
83
68
  write.string(name),
84
69
  version >= 8 ? write.i32(bricks) : EMPTY_ARR,
@@ -91,15 +76,15 @@ export default function writeBrs(save) {
91
76
  // write the save preview if it exists
92
77
  version >= 8
93
78
  ? concat(
94
- new Uint8Array([save.preview ? 1 : 0]),
95
- save.preview ? write.i32(get(save, 'preview.length', 0)) : EMPTY_ARR, // <- Sorry @Uxie https://i.imgur.com/hSRxdbf.png
96
- get(save, 'preview', EMPTY_ARR),
97
- )
79
+ new Uint8Array([save.preview ? 1 : 0]),
80
+ save.preview ? write.i32(get(save, 'preview.length', 0)) : EMPTY_ARR, // <- Sorry @Uxie https://i.imgur.com/hSRxdbf.png
81
+ get(save, 'preview', EMPTY_ARR),
82
+ )
98
83
  : EMPTY_ARR,
99
84
 
100
85
  // Bricks
101
86
  write.compressed(write.bits()
102
- .each(save.bricks, function(brick, i) {
87
+ .each(save.bricks, function (brick, i) {
103
88
  this.align();
104
89
  this.int(get(brick, 'asset_name_index', 0), numAssets);
105
90
 
@@ -108,6 +93,7 @@ export default function writeBrs(save) {
108
93
  if (!isSingularity) {
109
94
  brick.size.map(s => this.uint_packed(s));
110
95
  }
96
+
111
97
  brick.position.map(s => this.int_packed(s));
112
98
  const orientation = (get(brick, 'direction', 4) << 2) | get(brick, 'rotation', 0);
113
99
  this.int(orientation, 24);
@@ -169,7 +155,7 @@ export default function writeBrs(save) {
169
155
  version >= 8 ? write.compressed(write.array(Object.keys(get(save, 'components', {})).filter(name => componentBricks[name]), name => concat(
170
156
  write.string(name),
171
157
  write.bits()
172
- .self(function() {
158
+ .self(function () {
173
159
  const component = save.components[name];
174
160
  const brick_indices = componentBricks[name];
175
161
  const properties = Object.entries(component.properties);
@@ -200,4 +186,205 @@ export default function writeBrs(save) {
200
186
  ))) : EMPTY_ARR,
201
187
  );
202
188
  return buff;
203
- }
189
+ }
190
+
191
+ const fs = require('fs');
192
+ const path = require('path');
193
+ const _ = require('lodash');
194
+
195
+ // Write the save to a file
196
+ const outfile = path.resolve('/mnt/c/Users/ISAAC/AppData/Local/Brickadia/Saved/Builds', 'evil.brs');
197
+
198
+ // Print some info
199
+ fs.writeFileSync(outfile, writeSave({
200
+ "version": 10,
201
+ "bricks": [
202
+ {
203
+ "asset_name_index": 0,
204
+ "size": [ 5, 5, 6 ],
205
+ "position": [0, 0, 0],
206
+ "direction": 4,
207
+ "rotation": 0,
208
+ "collision": {
209
+ "player": true,
210
+ "weapon": true,
211
+ "interaction": true,
212
+ "tool": true
213
+ },
214
+ "visibility": true,
215
+ "material_index": 3,
216
+ "physical_index": 0,
217
+ "material_intensity": 5,
218
+ "color": 0,
219
+ "owner_index": 1,
220
+ "components": {
221
+ "BCD_AudioEmitter": {
222
+ "AudioDescriptor": "BA_AMB_Component_Fires_Fireplace_1",
223
+ "VolumeMultiplier": 1,
224
+ "PitchMultiplier": 1,
225
+ "InnerRadius": 15,
226
+ "MaxDistance": 400,
227
+ "bSpatialization": true
228
+ },
229
+ "BCD_ItemSpawn": {
230
+ "PickupClass": "BP_ItemPickup_StickGrenade",
231
+ "bPickupEnabled": true,
232
+ "bPickupRespawnOnMinigameReset": true,
233
+ "PickupMinigameResetRespawnDelay": 0,
234
+ "bPickupAutoDisableOnPickup": true,
235
+ "PickupRespawnTime": 0.1,
236
+ "PickupOffsetDirection": 4,
237
+ "PickupOffsetDistance": 5,
238
+ "PickupRotation": [ 0, 0, 5 ],
239
+ "PickupScale": 1,
240
+ "bPickupAnimationEnabled": true,
241
+ "PickupAnimationAxis": 2,
242
+ "bPickupAnimationAxisLocal": false,
243
+ "PickupSpinSpeed": 0.1,
244
+ "PickupBobSpeed": 0.1,
245
+ "PickupBobHeight": 4,
246
+ "PickupAnimationPhase": 0.1515854299068451
247
+ },
248
+ "BCD_PointLight": {
249
+ "bMatchBrickShape": true,
250
+ "Brightness": 15,
251
+ "Radius": 500,
252
+ "Color": [
253
+ 255,
254
+ 255,
255
+ 255,
256
+ 255
257
+ ],
258
+ "bUseBrickColor": true,
259
+ "bCastShadows": true
260
+ },
261
+ "BCD_SpotLight": {
262
+ "Rotation": [
263
+ 270,
264
+ 0,
265
+ 0
266
+ ],
267
+ "InnerConeAngle": 30,
268
+ "OuterConeAngle": 0,
269
+ "Brightness": 15,
270
+ "Radius": 150,
271
+ "Color": [
272
+ 0,
273
+ 255,
274
+ 255,
275
+ 255
276
+ ],
277
+ "bUseBrickColor": true,
278
+ "bCastShadows": true
279
+ }
280
+ }
281
+ },
282
+ ],
283
+ "map": "Plate",
284
+ "author": {
285
+ "name": "cake",
286
+ "id": "60eab7e7-9aa9-4647-83e4-6ce4ec66ae7d"
287
+ },
288
+ "description": "a".repeat(5),
289
+ "host": {
290
+ "name": "cake",
291
+ "id": "60eab7e7-9aa9-4647-83e4-6ce4ec66ae7d"
292
+ },
293
+ "mods": [],
294
+ "brick_assets": ["PB_DefaultBrick"],
295
+ "colors": [
296
+ [255, 255, 255, 255],
297
+ ],
298
+ "materials": [
299
+ "BMC_Hidden",
300
+ "BMC_Ghost",
301
+ "BMC_Ghost_Fail",
302
+ "BMC_Plastic",
303
+ "BMC_Glass",
304
+ "BMC_Glow",
305
+ "BMC_Metallic",
306
+ "BMC_Hologram"
307
+ ],
308
+ "brick_owners": [
309
+ {
310
+ "id": "60eab7e7-9aa9-4647-83e4-6ce4ec66ae7d",
311
+ "name": "cake",
312
+ "bricks": 69
313
+ },
314
+ ],
315
+ "physical_materials": [
316
+ "BPMC_Default"
317
+ ],
318
+ "gameVersion": 6880,
319
+ "components": {
320
+ "BCD_AudioEmitter": {
321
+ "version": 1,
322
+ "brick_indices": [
323
+ 0
324
+ ],
325
+ "properties": {
326
+ "AudioDescriptor": "Object",
327
+ "VolumeMultiplier": "Float",
328
+ "PitchMultiplier": "Float",
329
+ "InnerRadius": "Float",
330
+ "MaxDistance": "Float",
331
+ "bSpatialization": "Boolean"
332
+ }
333
+ },
334
+ "BCD_ItemSpawn": {
335
+ "version": -1,
336
+ "brick_indices": [
337
+ 0
338
+ ],
339
+ "properties": {
340
+ "PickupClass": "Class",
341
+ "bPickupEnabled": "Boolean",
342
+ "bPickupRespawnOnMinigameReset": "Boolean",
343
+ "PickupMinigameResetRespawnDelay": "Float",
344
+ "bPickupAutoDisableOnPickup": "Boolean",
345
+ "PickupRespawnTime": "Float",
346
+ "PickupOffsetDirection": "Byte",
347
+ "PickupOffsetDistance": "Float",
348
+ "PickupRotation": "Rotator",
349
+ "PickupScale": "Float",
350
+ "bPickupAnimationEnabled": "Boolean",
351
+ "PickupAnimationAxis": "Byte",
352
+ "bPickupAnimationAxisLocal": "Boolean",
353
+ "PickupSpinSpeed": "Float",
354
+ "PickupBobSpeed": "Float",
355
+ "PickupBobHeight": "Float",
356
+ "PickupAnimationPhase": "Float"
357
+ },
358
+ },
359
+ "BCD_PointLight": {
360
+ "version": 1,
361
+ "brick_indices": [
362
+ 0
363
+ ],
364
+ "properties": {
365
+ "bMatchBrickShape": "Boolean",
366
+ "Brightness": "Float",
367
+ "Radius": "Float",
368
+ "Color": "Color",
369
+ "bUseBrickColor": "Boolean",
370
+ "bCastShadows": "Boolean"
371
+ }
372
+ },
373
+ "BCD_SpotLight": {
374
+ "version": 1,
375
+ "brick_indices": [
376
+ 0
377
+ ],
378
+ "properties": {
379
+ "Rotation": "Rotator",
380
+ "InnerConeAngle": "Float",
381
+ "OuterConeAngle": "Float",
382
+ "Brightness": "Float",
383
+ "Radius": "Float",
384
+ "Color": "Color",
385
+ "bUseBrickColor": "Boolean",
386
+ "bCastShadows": "Boolean"
387
+ }
388
+ }
389
+ }
390
+ }));