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.
- package/README.md +41 -8
- package/dist/bin/mca-find-signs.d.ts +3 -0
- package/dist/bin/mca-find-signs.d.ts.map +1 -0
- package/dist/bin/mca-find-signs.js +148 -0
- package/dist/bin/mca-find-signs.js.map +1 -0
- package/dist/bin/mca-remove-chunks.d.ts +3 -0
- package/dist/bin/mca-remove-chunks.d.ts.map +1 -0
- package/dist/bin/mca-remove-chunks.js +144 -0
- package/dist/bin/mca-remove-chunks.js.map +1 -0
- package/dist/bin/nbt-get-player-location.js +18 -9
- package/dist/bin/nbt-get-player-location.js.map +1 -1
- package/dist/lib/anvil.d.ts.map +1 -1
- package/dist/lib/anvil.js +4 -3
- package/dist/lib/anvil.js.map +1 -1
- package/dist/lib/bit-data.js +2 -2
- package/dist/lib/bit-data.js.map +1 -1
- package/dist/lib/chunk.d.ts.map +1 -1
- package/dist/lib/chunk.js +12 -7
- package/dist/lib/chunk.js.map +1 -1
- package/dist/nbt/snbt-parse.d.ts.map +1 -1
- package/dist/nbt/snbt-parse.js +7 -4
- package/dist/nbt/snbt-parse.js.map +1 -1
- package/dist/nbt/snbt-to-nbt.js +14 -4
- package/dist/nbt/snbt-to-nbt.js.map +1 -1
- package/package.json +2 -2
- package/src/bin/mca-find-signs.ts +214 -0
- package/src/bin/mca-remove-chunks.ts +183 -0
- package/src/bin/nbt-get-player-location.ts +19 -11
- package/src/lib/anvil.ts +5 -3
- package/src/lib/bit-data.ts +2 -2
- package/src/lib/chunk.ts +15 -7
- package/src/nbt/snbt-parse.ts +7 -4
- package/src/nbt/snbt-to-nbt.ts +63 -20
- package/dist/bin/mca-find-chunks-with-signs.d.ts +0 -3
- package/dist/bin/mca-find-chunks-with-signs.d.ts.map +0 -1
- package/dist/bin/mca-find-chunks-with-signs.js +0 -79
- package/dist/bin/mca-find-chunks-with-signs.js.map +0 -1
- package/dist/bin/mca-trim-chunks-without-signs.d.ts +0 -3
- package/dist/bin/mca-trim-chunks-without-signs.d.ts.map +0 -1
- package/dist/bin/mca-trim-chunks-without-signs.js +0 -117
- package/dist/bin/mca-trim-chunks-without-signs.js.map +0 -1
- package/src/bin/mca-find-chunks-with-signs.ts +0 -109
- 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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
/**
|
package/src/lib/bit-data.ts
CHANGED
|
@@ -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
|
-
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
|
|
89
|
-
return;
|
|
89
|
+
if (typeof x?.data === 'number' && typeof z?.data === 'number') {
|
|
90
|
+
return [x.data, z.data];
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
|
|
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
|
/**
|
package/src/nbt/snbt-parse.ts
CHANGED
|
@@ -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[
|
|
134
|
+
matches[1].toLowerCase() === 'true' ? '1' : '0'
|
|
134
135
|
);
|
|
135
136
|
|
|
136
|
-
return matches[
|
|
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
|
|
package/src/nbt/snbt-to-nbt.ts
CHANGED
|
@@ -45,7 +45,10 @@ function snbtToNbtByte(token: SnbtToken): NbtByte {
|
|
|
45
45
|
return new NbtByte(parseInt(token.content || ''));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function snbtToNbtByteArray(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
148
|
+
throw new Error(
|
|
149
|
+
`${err} - Unexpected end of SNBT data while parsing int array`
|
|
150
|
+
);
|
|
125
151
|
}
|
|
126
152
|
|
|
127
|
-
|
|
128
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Expecting a comma but found ${snbtData.showToken(token)}`
|
|
247
|
+
);
|
|
205
248
|
}
|
|
206
249
|
|
|
207
250
|
token = snbtData.token();
|
|
@@ -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 +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":""}
|