mca-json 1.0.4 → 1.1.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.
Files changed (43) hide show
  1. package/README.md +41 -8
  2. package/dist/bin/mca-find-signs.d.ts +3 -0
  3. package/dist/bin/mca-find-signs.d.ts.map +1 -0
  4. package/dist/bin/mca-find-signs.js +148 -0
  5. package/dist/bin/mca-find-signs.js.map +1 -0
  6. package/dist/bin/mca-remove-chunks.d.ts +3 -0
  7. package/dist/bin/mca-remove-chunks.d.ts.map +1 -0
  8. package/dist/bin/mca-remove-chunks.js +144 -0
  9. package/dist/bin/mca-remove-chunks.js.map +1 -0
  10. package/dist/bin/nbt-get-player-location.js +18 -9
  11. package/dist/bin/nbt-get-player-location.js.map +1 -1
  12. package/dist/lib/anvil.d.ts.map +1 -1
  13. package/dist/lib/anvil.js +4 -3
  14. package/dist/lib/anvil.js.map +1 -1
  15. package/dist/lib/bit-data.js +2 -2
  16. package/dist/lib/bit-data.js.map +1 -1
  17. package/dist/lib/chunk.d.ts.map +1 -1
  18. package/dist/lib/chunk.js +12 -7
  19. package/dist/lib/chunk.js.map +1 -1
  20. package/dist/nbt/snbt-parse.d.ts.map +1 -1
  21. package/dist/nbt/snbt-parse.js +7 -4
  22. package/dist/nbt/snbt-parse.js.map +1 -1
  23. package/dist/nbt/snbt-to-nbt.js +14 -4
  24. package/dist/nbt/snbt-to-nbt.js.map +1 -1
  25. package/package.json +2 -2
  26. package/src/bin/mca-find-signs.ts +214 -0
  27. package/src/bin/mca-remove-chunks.ts +183 -0
  28. package/src/bin/nbt-get-player-location.ts +19 -11
  29. package/src/lib/anvil.ts +5 -3
  30. package/src/lib/bit-data.ts +2 -2
  31. package/src/lib/chunk.ts +15 -7
  32. package/src/nbt/snbt-parse.ts +7 -4
  33. package/src/nbt/snbt-to-nbt.ts +63 -20
  34. package/dist/bin/mca-find-chunks-with-signs.d.ts +0 -3
  35. package/dist/bin/mca-find-chunks-with-signs.d.ts.map +0 -1
  36. package/dist/bin/mca-find-chunks-with-signs.js +0 -79
  37. package/dist/bin/mca-find-chunks-with-signs.js.map +0 -1
  38. package/dist/bin/mca-trim-chunks-without-signs.d.ts +0 -3
  39. package/dist/bin/mca-trim-chunks-without-signs.d.ts.map +0 -1
  40. package/dist/bin/mca-trim-chunks-without-signs.js +0 -117
  41. package/dist/bin/mca-trim-chunks-without-signs.js.map +0 -1
  42. package/src/bin/mca-find-chunks-with-signs.ts +0 -109
  43. package/src/bin/mca-trim-chunks-without-signs.ts +0 -146
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+
3
+ import debug from 'debug';
4
+ import neodoc from 'neodoc';
5
+ import { Anvil } from '../lib/anvil';
6
+ import { readFile, writeFile } from 'node:fs/promises';
7
+
8
+ const debugLog = debug('mca-remove-chunks');
9
+ const args: {
10
+ '--dry-run'?: boolean;
11
+ '--remove'?: string[];
12
+ '--remove-list'?: string;
13
+ '--preserve'?: string[];
14
+ '--preserve-list'?: string;
15
+ '--verbose'?: boolean;
16
+ MCA_FILE?: string[];
17
+ } = neodoc.run(
18
+ `Usage: mca-remove-chunks [OPTIONS] MCA_FILE...
19
+
20
+ Removes chunks from MCA files (region/r.*.*.mca or entities/r.*.*.mca).
21
+
22
+ This does not change the file size. Similar to deleting a file on a hard drive,
23
+ the space is marked as free but not actually removed. Minecraft will reuse or reclaim the space when the region file is next modified.
24
+
25
+ You can either specify chunk coordinates to preserve or chunk coordinates to remove, but not both.
26
+
27
+ Options:
28
+ -h, --help Show this message.
29
+ -n, --dry-run Do not write changes to the file.
30
+ --remove CHUNK_COORDINATES...
31
+ Remove chunks with these coordinates. Coordinates are in
32
+ the format "x,z". May be specified multiple times.
33
+ --remove-list FILE
34
+ Remove chunks with coordinates specified in a text file.
35
+ Each line of the file should have coordinates in the format
36
+ "x,z". Safe to combine with --remove option.
37
+ --preserve CHUNK_COORDINATES...
38
+ Do not remove chunks with these coordinates. Coordinates
39
+ are in the format "x,z". May be specified multiple times.
40
+ --preserve-list FILE
41
+ Preserve chunks with coordinates specified in a text file.
42
+ Each line of the file should have coordinates in the format
43
+ "x,z". Safe to combine with --preserve option.
44
+ -v, --verbose Show more information during processing.
45
+ `,
46
+ {
47
+ argv: [...process.argv].slice(2),
48
+ laxPlacement: true,
49
+ }
50
+ );
51
+
52
+ main();
53
+
54
+ async function main() {
55
+ if (!args.MCA_FILE?.length) {
56
+ console.error('No files specified.');
57
+ process.exit(1);
58
+ }
59
+
60
+ if (args['--remove-list']) {
61
+ const coordinates = await readCoordinates(args['--remove-list']);
62
+
63
+ if (!args['--remove']) {
64
+ args['--remove'] = [];
65
+ }
66
+
67
+ args['--remove'].push(...coordinates);
68
+ }
69
+
70
+ if (args['--preserve-list']) {
71
+ const coordinates = await readCoordinates(args['--preserve-list']);
72
+
73
+ if (!args['--preserve']) {
74
+ args['--preserve'] = [];
75
+ }
76
+
77
+ args['--preserve'].push(...coordinates);
78
+ }
79
+
80
+ if (args['--remove'] && args['--preserve']) {
81
+ console.error('Cannot specify both --remove and --preserve options.');
82
+ process.exit(1);
83
+ }
84
+
85
+ if (!args['--remove'] && !args['--preserve']) {
86
+ console.error('Must specify either --remove or --preserve option.');
87
+ process.exit(1);
88
+ }
89
+
90
+ if (args['--preserve'] && args['--verbose']) {
91
+ console.log(`Preserving coordinates: ${args['--preserve'].join(' ')}`);
92
+ }
93
+
94
+ if (args['--remove'] && args['--verbose']) {
95
+ console.log(`Removing coordinates: ${args['--remove'].join(' ')}`);
96
+ }
97
+
98
+ const determinator = makeDeterminator();
99
+
100
+ for (const file of args.MCA_FILE) {
101
+ try {
102
+ await processFile(file, determinator);
103
+ } catch (e) {
104
+ console.error(`Error processing file ${file}: ${e}`);
105
+
106
+ process.exit(1);
107
+ }
108
+ }
109
+ }
110
+
111
+ async function processFile(
112
+ filename: string,
113
+ determinator: (chunkKey: string) => boolean
114
+ ) {
115
+ debugLog(`Reading file: ${filename}`);
116
+
117
+ if (args['--verbose']) {
118
+ console.log(`Reading file: ${filename}`);
119
+ }
120
+
121
+ const contents = await readFile(filename);
122
+ const anvil = Anvil.fromBuffer(contents.buffer);
123
+ const chunks = anvil.getAllChunks();
124
+ let changes = 0;
125
+
126
+ for (const chunk of chunks) {
127
+ const chunkKey = chunk.chunkKey() || '';
128
+
129
+ if (determinator(chunkKey)) {
130
+ debugLog(`Preserving chunk: ${chunkKey}`);
131
+
132
+ if (args['--verbose']) {
133
+ console.log(`Preserving chunk: ${chunkKey}`);
134
+ }
135
+ } else {
136
+ debugLog(`Removing chunk: ${chunkKey}`);
137
+
138
+ if (args['--verbose']) {
139
+ console.log(`Removing chunk: ${chunkKey}`);
140
+ }
141
+
142
+ anvil.deleteChunk(chunk);
143
+ changes += 1;
144
+ }
145
+ }
146
+
147
+ if (args['--verbose']) {
148
+ console.log(`Chunks removed: ${changes}/${chunks.length}`);
149
+ }
150
+
151
+ if (changes) {
152
+ if (args['--dry-run']) {
153
+ console.log(`DRY RUN: Would write changes to file: ${filename}`);
154
+ } else {
155
+ console.log(`Writing changes to file: ${filename}`);
156
+ const modifiedBuffer = Buffer.from(anvil.buffer());
157
+ await writeFile(filename, modifiedBuffer);
158
+ }
159
+ }
160
+ }
161
+
162
+ function makeDeterminator() {
163
+ if (args['--preserve']) {
164
+ const preservedCoordinates = new Set(args['--preserve']);
165
+
166
+ return (chunkKey: string) => preservedCoordinates.has(chunkKey);
167
+ } else if (args['--remove']) {
168
+ const removedCoordinates = new Set(args['--remove']);
169
+
170
+ return (chunkKey: string) => !removedCoordinates.has(chunkKey);
171
+ } else {
172
+ throw new Error('Invalid state: no determinator could be created.');
173
+ }
174
+ }
175
+
176
+ async function readCoordinates(filename: string): Promise<string[]> {
177
+ const contents = await readFile(filename, 'utf-8');
178
+ const lines = contents
179
+ .split('\n')
180
+ .map((line) => line.trim())
181
+ .filter((line) => line.length > 0);
182
+ return lines;
183
+ }
@@ -19,14 +19,16 @@ const args: {
19
19
  Scans a player NBT data file for the player location and prints it to stdout.
20
20
  NBT files can be compressed or uncompressed and this tool can handle both. If the filename is "-" or not provided, this will read from stdin.
21
21
 
22
- The output shows the following tab delimited fields:
23
-
24
- * Dimension: "minecraft:overworld", "minecraft:the_nether", or "minecraft:the_end"
25
- * X: The player's X coordinate, rounded
26
- * Y: The player's Y coordinate, rounded
27
- * Z: The player's Z coordinate, rounded
28
- * chunkX: The player's chunk X coordinate
29
- * chunkZ: The player's chunk Z coordinate
22
+ The output is JSON with the following fields:
23
+
24
+ {
25
+ "chunkX": 7, # player's chunk X coordinate
26
+ "chunkZ": -29 # player's chunk Z coordinate
27
+ "x": 123, # player's X coordinate, rounded
28
+ "y": 64, # player's Y coordinate, rounded
29
+ "z": -456, # player's Z coordinate, rounded
30
+ "dimension": "minecraft:overworld", # or "minecraft:the_nether" or "minecraft:the_end"
31
+ }
30
32
 
31
33
  Options:
32
34
  -h, --help Show this message.
@@ -96,7 +98,13 @@ async function processFile(filename: string) {
96
98
 
97
99
  const chunkX = Math.floor(x / 16);
98
100
  const chunkZ = Math.floor(z / 16);
99
-
100
- const output = [dimension, Math.round(x), Math.round(y), Math.round(z), chunkX, chunkZ];
101
- console.log(output.join('\t'));
101
+ const output = {
102
+ chunkX,
103
+ chunkZ,
104
+ x: Math.round(x),
105
+ y: Math.round(y),
106
+ z: Math.round(z),
107
+ dimension,
108
+ };
109
+ console.log(JSON.stringify(output, null, 4));
102
110
  }
package/src/lib/anvil.ts CHANGED
@@ -61,9 +61,11 @@ export class Anvil {
61
61
 
62
62
  // Clear cache before modifying location entries
63
63
  this.cachedLocationEntries = null;
64
- this.data.seek(index * 4);
65
- this.data.setNByteInteger(0, 3);
66
- this.data.setByte(0);
64
+ this.data.seek(index * 4); // Go to the pointer location for this chunk
65
+ this.data.setNByteInteger(0, 3); // Erase the offset
66
+ this.data.setByte(0); // Erase the sector count
67
+
68
+ // The actual chunk data is still present in the file.
67
69
  }
68
70
 
69
71
  /**
@@ -3,8 +3,8 @@ import { NbtLongArray } from '../nbt/nbt-long-array';
3
3
 
4
4
  const debugLog = debug('bit-data');
5
5
 
6
- // At most, we will have 7 bits to mask
7
- const MASK = [0, 1, 3, 7, 15, 31, 63, 127];
6
+ // At most, we will have 8 bits to mask
7
+ const MASK = [0, 1, 3, 7, 15, 31, 63, 127, 255];
8
8
 
9
9
  export class BitData {
10
10
  static fromLongArrayTag(tag: NbtLongArray): BitData {
package/src/lib/chunk.ts CHANGED
@@ -12,6 +12,7 @@ import { Coords2d, Coords3d } from '../types/coords';
12
12
  import { NbtBase } from '../nbt/nbt-base';
13
13
  import { NbtCompound } from '../nbt/nbt-compound';
14
14
  import { NbtInt } from '../nbt/nbt-int';
15
+ import { NbtIntArray } from '../nbt/nbt-int-array';
15
16
  import { NbtList } from '../nbt/nbt-list';
16
17
  import { NbtLong } from '../nbt/nbt-long';
17
18
  import { NbtLongArray } from '../nbt/nbt-long-array';
@@ -75,21 +76,28 @@ export class Chunk {
75
76
  */
76
77
  chunkCoordinates(): Coords2d | undefined {
77
78
  const x =
78
- // 1.18+
79
+ // 1.18+ region chunks
79
80
  this.rootNbt.findChild<NbtInt>('xPos') ||
80
- // up to 1.17
81
+ // up to 1.17 region chunks
81
82
  this.rootNbt.findChild<NbtInt>('Level/xPos');
82
83
  const z =
83
- // 1.18+
84
+ // 1.18+ region chunks
84
85
  this.rootNbt.findChild<NbtInt>('zPos') ||
85
- // up to 1.17
86
+ // up to 1.17 region chunks
86
87
  this.rootNbt.findChild<NbtInt>('Level/zPos');
87
88
 
88
- if (typeof x?.data !== 'number' || typeof z?.data !== 'number') {
89
- return;
89
+ if (typeof x?.data === 'number' && typeof z?.data === 'number') {
90
+ return [x.data, z.data];
90
91
  }
91
92
 
92
- return [x.data, z.data];
93
+ // Check for entity chunks
94
+ const position = this.rootNbt.findChild<NbtIntArray>('Position');
95
+
96
+ if (position && position.data.length === 2) {
97
+ return [position.data[0], position.data[1]];
98
+ }
99
+
100
+ return;
93
101
  }
94
102
 
95
103
  /**
@@ -38,6 +38,7 @@ const tokenParsers: TokenParser[] = [
38
38
  type: 'WHITESPACE',
39
39
  pattern: /^\s+/,
40
40
  callback: (_pos, _type, _snbtData, matches) => {
41
+ // Do not generate a token for whitespace.
41
42
  return matches[0].length;
42
43
  },
43
44
  },
@@ -130,10 +131,10 @@ const tokenParsers: TokenParser[] = [
130
131
  snbtData.addToken(
131
132
  pos,
132
133
  type,
133
- matches[0].toLowerCase() === 'true' ? '1' : '0'
134
+ matches[1].toLowerCase() === 'true' ? '1' : '0'
134
135
  );
135
136
 
136
- return matches[0].length;
137
+ return matches[1].length;
137
138
  },
138
139
  },
139
140
  {
@@ -228,9 +229,10 @@ const tokenParsers: TokenParser[] = [
228
229
  ];
229
230
 
230
231
  // Creates a regular expression that will start at the beginning of the string
231
- // and conclude with an "end of token" marker at the end.
232
+ // and conclude with an "end of token" marker at the end. The lookahead will
233
+ // not capture the end of the token marker.
232
234
  function anchorPattern(pattern: string) {
233
- return new RegExp(`^${pattern}\s*(?:$|,|}|]|:)`);
235
+ return new RegExp(`^${pattern}\s*(?=$|,|}|]|:)`);
234
236
  }
235
237
 
236
238
  export function snbtParse(snbt: string) {
@@ -249,6 +251,7 @@ export function snbtParse(snbt: string) {
249
251
  throw new Error(`Unexpected token at position ${pos}`);
250
252
  }
251
253
 
254
+ debugLog(`Increment position by ${increment}`);
252
255
  pos += increment;
253
256
  }
254
257
 
@@ -45,7 +45,10 @@ function snbtToNbtByte(token: SnbtToken): NbtByte {
45
45
  return new NbtByte(parseInt(token.content || ''));
46
46
  }
47
47
 
48
- function snbtToNbtByteArray(_token: SnbtToken, snbtData: SnbtData): NbtByteArray {
48
+ function snbtToNbtByteArray(
49
+ _token: SnbtToken,
50
+ snbtData: SnbtData
51
+ ): NbtByteArray {
49
52
  const bytes: number[] = [];
50
53
  let token = snbtData.token();
51
54
 
@@ -55,11 +58,15 @@ function snbtToNbtByteArray(_token: SnbtToken, snbtData: SnbtData): NbtByteArray
55
58
  try {
56
59
  token = snbtData.token();
57
60
  } catch (err) {
58
- throw new Error(`${err} - Unexpected end of SNBT data while parsing byte array`);
61
+ throw new Error(
62
+ `${err} - Unexpected end of SNBT data while parsing byte array`
63
+ );
59
64
  }
60
65
 
61
66
  if (token.type !== 'COMMA') {
62
- throw new Error(`Expecting a comma but found ${snbtData.showToken(token)}`);
67
+ throw new Error(
68
+ `Expecting a comma but found ${snbtData.showToken(token)}`
69
+ );
63
70
  }
64
71
 
65
72
  token = snbtData.token();
@@ -74,7 +81,9 @@ function snbtToNbtCompound(_token: SnbtToken, snbtData: SnbtData): NbtCompound {
74
81
 
75
82
  while (token && token.type !== 'COMPOUND_END') {
76
83
  if (token.type !== 'STRING') {
77
- throw new Error(`Expecting a string but found ${snbtData.showToken(token)}`);
84
+ throw new Error(
85
+ `Expecting a string but found ${snbtData.showToken(token)}`
86
+ );
78
87
  }
79
88
 
80
89
  const name = token.content;
@@ -83,17 +92,31 @@ function snbtToNbtCompound(_token: SnbtToken, snbtData: SnbtData): NbtCompound {
83
92
  try {
84
93
  colon = snbtData.token();
85
94
  } catch (err) {
86
- throw new Error(`${err} - Unexpected end of SNBT data while looking for colon`);
95
+ throw new Error(
96
+ `${err} - Unexpected end of SNBT data while looking for colon`
97
+ );
87
98
  }
88
99
 
89
100
  if (colon.type !== 'COLON') {
90
- throw new Error(`Expecting a colon but found ${snbtData.showToken(colon)}`);
101
+ throw new Error(
102
+ `Expecting a colon but found ${snbtData.showToken(colon)}`
103
+ );
91
104
  }
92
105
 
93
106
  const nbt = snbtToNbt(snbtData);
94
107
  nbt.name = name;
95
108
  data.push(nbt);
96
109
  token = snbtData.token();
110
+
111
+ if (token && token.type === 'COMMA') {
112
+ try {
113
+ token = snbtData.token();
114
+ } catch (err) {
115
+ throw new Error(
116
+ `${err} - Unexpected end of SNBT data while looking for token after comma`
117
+ );
118
+ }
119
+ }
97
120
  }
98
121
 
99
122
  return new NbtCompound(data);
@@ -115,20 +138,23 @@ function snbtToNbtIntArray(_token: SnbtToken, snbtData: SnbtData): NbtIntArray {
115
138
  const ints: number[] = [];
116
139
  let token = snbtData.token();
117
140
 
118
- while (token && token.type === 'INT') {
141
+ // The fallback double parser can be used for integers too.
142
+ while (token && (token.type === 'INT' || token.type === 'DOUBLE')) {
119
143
  ints.push(parseInt(token.content || ''));
120
144
 
121
145
  try {
122
146
  token = snbtData.token();
123
147
  } catch (err) {
124
- throw new Error(`${err} - Unexpected end of SNBT data while parsing int array`);
148
+ throw new Error(
149
+ `${err} - Unexpected end of SNBT data while parsing int array`
150
+ );
125
151
  }
126
152
 
127
- if (token.type !== 'COMMA') {
128
- throw new Error(`Expecting a comma but found ${snbtData.showToken(token)}`);
153
+ // If we see a comma, we expect another number after it. If not, we
154
+ // expect the end of the array.
155
+ if (token.type === 'COMMA') {
156
+ token = snbtData.token();
129
157
  }
130
-
131
- token = snbtData.token();
132
158
  }
133
159
 
134
160
  return new NbtIntArray(ints);
@@ -141,7 +167,9 @@ function snbtToNbtList(_token: SnbtToken, snbtData: SnbtData): NbtList<any> {
141
167
  try {
142
168
  token = snbtData.token();
143
169
  } catch (err) {
144
- throw new Error(`${err} - Unexpected end of SNBT data while parsing list at beginning`);
170
+ throw new Error(
171
+ `${err} - Unexpected end of SNBT data while parsing list at beginning`
172
+ );
145
173
  }
146
174
 
147
175
  while (token.type !== 'LIST_END') {
@@ -161,22 +189,30 @@ function snbtToNbtList(_token: SnbtToken, snbtData: SnbtData): NbtList<any> {
161
189
  try {
162
190
  token = snbtData.token();
163
191
  } catch (err) {
164
- throw new Error(`${err} - Unexpected end of SNBT data while looking for comma or end of list`);
192
+ throw new Error(
193
+ `${err} - Unexpected end of SNBT data while looking for comma or end of list`
194
+ );
165
195
  }
166
196
  } else if (token.type !== 'LIST_END') {
167
- throw new Error(`Expecting a comma or list end but found ${snbtData.showToken(token)}`);
197
+ throw new Error(
198
+ `Expecting a comma or list end but found ${snbtData.showToken(token)}`
199
+ );
168
200
  }
169
201
  }
170
202
 
171
203
  if (!list.length) {
172
- throw new Error('Empty list not allowed in SNBT because the type cannot be inferred');
204
+ throw new Error(
205
+ 'Empty list not allowed in SNBT because the type cannot be inferred'
206
+ );
173
207
  }
174
208
 
175
209
  const firstType = list[0].type;
176
210
 
177
211
  for (let i = 1; i < list.length; i++) {
178
212
  if (list[i].type !== firstType) {
179
- throw new Error(`List contains mixed types: ${firstType} and ${list[i].type}`);
213
+ throw new Error(
214
+ `List contains mixed types: ${firstType} and ${list[i].type}`
215
+ );
180
216
  }
181
217
  }
182
218
 
@@ -187,7 +223,10 @@ function snbtToNbtLong(token: SnbtToken): NbtLong {
187
223
  return new NbtLong(BigInt(token.content.slice(0, -1) || ''));
188
224
  }
189
225
 
190
- function snbtToNbtLongArray(_token: SnbtToken, snbtData: SnbtData): NbtLongArray {
226
+ function snbtToNbtLongArray(
227
+ _token: SnbtToken,
228
+ snbtData: SnbtData
229
+ ): NbtLongArray {
191
230
  const longs: bigint[] = [];
192
231
  let token = snbtData.token();
193
232
 
@@ -197,11 +236,15 @@ function snbtToNbtLongArray(_token: SnbtToken, snbtData: SnbtData): NbtLongArray
197
236
  try {
198
237
  token = snbtData.token();
199
238
  } catch (err) {
200
- throw new Error(`${err} - Unexpected end of SNBT data while parsing long array`);
239
+ throw new Error(
240
+ `${err} - Unexpected end of SNBT data while parsing long array`
241
+ );
201
242
  }
202
243
 
203
244
  if (token.type !== 'COMMA') {
204
- throw new Error(`Expecting a comma but found ${snbtData.showToken(token)}`);
245
+ throw new Error(
246
+ `Expecting a comma but found ${snbtData.showToken(token)}`
247
+ );
205
248
  }
206
249
 
207
250
  token = snbtData.token();
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=mca-find-chunks-with-signs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mca-find-chunks-with-signs.d.ts","sourceRoot":"","sources":["../../src/bin/mca-find-chunks-with-signs.ts"],"names":[],"mappings":""}
@@ -1,79 +0,0 @@
1
- #!/usr/bin/env node
2
- import debug from 'debug';
3
- import neodoc from 'neodoc';
4
- import { Anvil } from '../lib/anvil';
5
- import { Sign } from '../block/sign';
6
- import { readFile } from 'node:fs/promises';
7
- const debugLog = debug('mca-find-chunks-with-signs');
8
- const args = neodoc.run(`Usage: mca-trim-chunks-without-signs [OPTIONS] MCA_FILE...
9
-
10
- Displays chunk X and Z coordinates, separated by a tab, for all chunks in MCA
11
- files that contain a sign block. Signs need to have at least one letter or
12
- number on them to differentiate them from naturally generated signs.
13
-
14
- Options:
15
- -h, --help Show this message.
16
- -v, --verbose Show more information during processing.
17
- `, {
18
- argv: [...process.argv].slice(2),
19
- laxPlacement: true,
20
- });
21
- if (!args.MCA_FILE?.length) {
22
- console.error('No files specified.');
23
- process.exit(1);
24
- }
25
- for (const file of args.MCA_FILE) {
26
- try {
27
- await processFile(file);
28
- }
29
- catch (e) {
30
- console.error(`Error processing file ${file}: ${e}`);
31
- process.exit(1);
32
- }
33
- }
34
- async function processFile(filename) {
35
- debugLog(`Reading file: ${filename}`);
36
- if (args['--verbose']) {
37
- console.log(`Reading file: ${filename}`);
38
- }
39
- const contents = await readFile(filename);
40
- const anvil = Anvil.fromBuffer(contents.buffer);
41
- const chunks = anvil.getAllChunks();
42
- for (const chunk of chunks) {
43
- const chunkKey = chunk.chunkKey() || '';
44
- if (processChunk(chunk)) {
45
- const coords = chunk.chunkCoordinates();
46
- if (!coords) {
47
- throw new Error(`Chunk ${chunkKey} has no coordinates.`);
48
- }
49
- console.log(`${coords[0]}\t${coords[1]}`);
50
- }
51
- }
52
- }
53
- function processChunk(chunk) {
54
- const blockNames = chunk.uniqueBlockNames();
55
- for (const blockName of blockNames) {
56
- if (blockName.match(/^minecraft:.+_sign$/)) {
57
- debugLog(`Found sign: ${blockName}`);
58
- for (const coords of chunk.findBlocksByName(blockName)) {
59
- const block = chunk.getBlock(coords);
60
- if (!(block instanceof Sign)) {
61
- throw new Error(`Block is supposed to be a sign: ${block.name}`);
62
- }
63
- const front = block.frontText();
64
- const back = block.backText();
65
- debugLog(`Front: ${JSON.stringify(front)}`);
66
- debugLog(`Back: ${JSON.stringify(back)}`);
67
- if ([...front, ...(back || [])].join('').match(/[a-z0-9]/i)) {
68
- debugLog(`Found a user-created sign. Preserving chunk.`);
69
- if (args['--verbose']) {
70
- console.log(`Found a user-created sign: ${JSON.stringify(front)} and ${JSON.stringify(back)}`);
71
- }
72
- return true;
73
- }
74
- }
75
- }
76
- }
77
- return false;
78
- }
79
- //# sourceMappingURL=mca-find-chunks-with-signs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mca-find-chunks-with-signs.js","sourceRoot":"","sources":["../../src/bin/mca-find-chunks-with-signs.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport debug from 'debug';\nimport neodoc from 'neodoc';\nimport { Anvil } from '../lib/anvil';\nimport { Chunk } from '../lib/chunk';\nimport { Sign } from '../block/sign';\nimport { readFile } from 'node:fs/promises';\n\nconst debugLog = debug('mca-find-chunks-with-signs');\nconst args: {\n '--verbose'?: boolean;\n MCA_FILE?: string[];\n} = neodoc.run(\n `Usage: mca-trim-chunks-without-signs [OPTIONS] MCA_FILE...\n\nDisplays chunk X and Z coordinates, separated by a tab, for all chunks in MCA\nfiles that contain a sign block. Signs need to have at least one letter or\nnumber on them to differentiate them from naturally generated signs.\n\nOptions:\n -h, --help Show this message.\n -v, --verbose Show more information during processing.\n`,\n {\n argv: [...process.argv].slice(2),\n laxPlacement: true,\n }\n);\n\nif (!args.MCA_FILE?.length) {\n console.error('No files specified.');\n process.exit(1);\n}\n\nfor (const file of args.MCA_FILE) {\n try {\n await processFile(file);\n } catch (e) {\n console.error(`Error processing file ${file}: ${e}`);\n\n process.exit(1);\n }\n}\n\nasync function processFile(filename: string) {\n debugLog(`Reading file: ${filename}`);\n\n if (args['--verbose']) {\n console.log(`Reading file: ${filename}`);\n }\n\n const contents = await readFile(filename);\n const anvil = Anvil.fromBuffer(contents.buffer);\n const chunks = anvil.getAllChunks();\n\n for (const chunk of chunks) {\n const chunkKey = chunk.chunkKey() || '';\n\n if (processChunk(chunk)) {\n const coords = chunk.chunkCoordinates();\n\n if (!coords) {\n throw new Error(`Chunk ${chunkKey} has no coordinates.`);\n }\n\n console.log(`${coords[0]}\\t${coords[1]}`);\n }\n }\n}\n\nfunction processChunk(chunk: Chunk): boolean {\n const blockNames = chunk.uniqueBlockNames();\n\n for (const blockName of blockNames) {\n if (blockName.match(/^minecraft:.+_sign$/)) {\n debugLog(`Found sign: ${blockName}`);\n\n for (const coords of chunk.findBlocksByName(blockName)) {\n const block = chunk.getBlock(coords);\n\n if (!(block instanceof Sign)) {\n throw new Error(\n `Block is supposed to be a sign: ${block.name}`\n );\n }\n\n const front = block.frontText();\n const back = block.backText();\n debugLog(`Front: ${JSON.stringify(front)}`);\n debugLog(`Back: ${JSON.stringify(back)}`);\n\n if ([...front, ...(back || [])].join('').match(/[a-z0-9]/i)) {\n debugLog(`Found a user-created sign. Preserving chunk.`);\n\n if (args['--verbose']) {\n console.log(\n `Found a user-created sign: ${JSON.stringify(front)} and ${JSON.stringify(back)}`\n );\n }\n\n return true;\n }\n }\n }\n }\n\n return false;\n}\n"]}
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=mca-trim-chunks-without-signs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mca-trim-chunks-without-signs.d.ts","sourceRoot":"","sources":["../../src/bin/mca-trim-chunks-without-signs.ts"],"names":[],"mappings":""}