minerender 2.1.0-alpha.91 → 2.1.0-alpha.93

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.
@@ -18872,6 +18872,7 @@ __export(index_browser_exports, {
18872
18872
  prefix: () => prefix,
18873
18873
  probeImageSize: () => probeImageSize,
18874
18874
  registerBrowserEnv: () => registerBrowserEnv,
18875
+ resolveLegacyBlock: () => resolveLegacyBlock,
18875
18876
  sampleEntityAnimation: () => sampleEntityAnimation,
18876
18877
  sampleEntityKeyframes: () => sampleEntityKeyframes,
18877
18878
  sampleFluid: () => sampleFluid,
@@ -23900,158 +23901,6 @@ function isBlockObject(obj) {
23900
23901
  init_FluidGeometry();
23901
23902
  init_FluidModelObject();
23902
23903
 
23903
- // src/model/multiblock/LitematicaParser.ts
23904
- init_MineRenderError();
23905
- var LitematicaParser = class {
23906
- /**
23907
- * Parses decoded NBT, preserving subregion placement and omitting air.
23908
- * Later entries in regions replace earlier cells, including with air.
23909
- * Size encloses all region bounds; positions retain the schematic origin.
23910
- * Metadata and scheduled ticks are not imported. Named regions can be placed individually.
23911
- */
23912
- static async parse(nbt) {
23913
- const tags = nbt.value;
23914
- const version2 = integer2(tags.Version, "Version");
23915
- if (version2 < 5 || version2 > 7) throw new MineRenderError(`Unsupported Litematica version ${version2}; expected 5, 6, or 7`);
23916
- const dataVersion = tags.MinecraftDataVersion === void 0 ? void 0 : integer2(tags.MinecraftDataVersion, "MinecraftDataVersion");
23917
- const blocks = /* @__PURE__ */ new Map();
23918
- const regions = [];
23919
- const min = [Infinity, Infinity, Infinity];
23920
- const max = [-Infinity, -Infinity, -Infinity];
23921
- for (const [name, tag] of Object.entries(compound(tags.Regions, "Regions"))) {
23922
- const region = readRegion(compound(tag, `region ${name}`), name, dataVersion, blocks);
23923
- regions.push(region);
23924
- for (let axis = 0; axis < 3; axis++) {
23925
- const start = minimum(region.position[axis], region.signedSize[axis]);
23926
- min[axis] = Math.min(min[axis], start);
23927
- max[axis] = Math.max(max[axis], start + region.size[axis]);
23928
- }
23929
- }
23930
- return {
23931
- size: regions.length ? min.map((value, axis) => max[axis] - value) : [0, 0, 0],
23932
- blocks: [...blocks.values()],
23933
- entities: regions.flatMap((region) => region.entities ?? []),
23934
- dataVersion,
23935
- regions
23936
- };
23937
- }
23938
- };
23939
- function readRegion(tags, name, dataVersion, combined) {
23940
- const position = blockPosition(compound(tags.Position, `region ${name} Position`));
23941
- const signedSize = blockPosition(compound(tags.Size, `region ${name} Size`));
23942
- if (signedSize.some((value) => value === 0)) throw new MineRenderError(`Litematica region ${name} dimensions must be nonzero`);
23943
- const size = signedSize.map(Math.abs);
23944
- const min = position.map((value, axis) => minimum(value, signedSize[axis]));
23945
- const [width, height, length] = size;
23946
- const volume = width * height * length;
23947
- const palette = compounds(tags.BlockStatePalette, "BlockStatePalette").map(blockState);
23948
- if (!palette.length) throw new MineRenderError(`Litematica region ${name} has no block palette`);
23949
- const bits = Math.max(2, Math.ceil(Math.log2(palette.length)));
23950
- if (!Number.isSafeInteger(volume * bits)) throw new MineRenderError(`Litematica region ${name} volume is too large`);
23951
- const packed = tags.BlockStates;
23952
- const expected = Math.ceil(volume * bits / 64);
23953
- if (packed?.type !== "longArray" || !Array.isArray(packed.value) || packed.value.length !== expected) {
23954
- throw new MineRenderError(`Litematica region ${name} BlockStates must contain ${expected} longs`);
23955
- }
23956
- const words = packed.value.map((pair) => {
23957
- if (!Array.isArray(pair) || pair.length !== 2 || !pair.every((value) => Number.isInteger(value) && value >= -2147483648 && value <= 4294967295)) {
23958
- throw new MineRenderError(`Litematica region ${name} has an invalid BlockStates long`);
23959
- }
23960
- return [pair[0] >>> 0, pair[1] >>> 0];
23961
- });
23962
- const blockEntities = /* @__PURE__ */ new Map();
23963
- for (const tile of compounds(tags.TileEntities, "TileEntities")) {
23964
- const pos = blockPosition(tile);
23965
- if (pos.some((value2, axis) => value2 < 0 || value2 >= size[axis])) {
23966
- throw new MineRenderError(`Litematica region ${name} block entity is outside its dimensions`);
23967
- }
23968
- const index = pos[0] + pos[2] * width + pos[1] * width * length;
23969
- if (blockEntities.has(index)) throw new MineRenderError(`Duplicate Litematica block entity at index ${index}`);
23970
- const value = { ...tile };
23971
- for (const [axis, key] of ["x", "y", "z"].entries()) value[key] = { type: "int", value: pos[axis] + min[axis] };
23972
- blockEntities.set(index, { type: "compound", value });
23973
- }
23974
- const blocks = [];
23975
- const mask = 2 ** bits - 1;
23976
- for (let index = 0; index < volume; index++) {
23977
- const word = Math.floor(index * bits / 64);
23978
- const shift = index * bits % 64;
23979
- const [high, low] = words[word];
23980
- let value = shift < 32 ? low >>> shift : high >>> shift - 32;
23981
- if (shift < 32 && shift + bits > 32) value |= high << 32 - shift;
23982
- if (shift + bits > 64) value |= words[word + 1][1] << 64 - shift;
23983
- const id = (value & mask) >>> 0;
23984
- const state = palette[id];
23985
- if (!state) throw new MineRenderError(`Litematica region ${name} palette index ${id} does not exist`);
23986
- const pos = [
23987
- index % width + min[0],
23988
- Math.floor(index / (width * length)) + min[1],
23989
- Math.floor(index / width) % length + min[2]
23990
- ];
23991
- const key = pos.join(",");
23992
- if (["minecraft:air", "minecraft:cave_air", "minecraft:void_air"].includes(state.type)) {
23993
- combined.delete(key);
23994
- continue;
23995
- }
23996
- const block = { type: state.type, properties: { ...state.properties }, position: pos, nbt: blockEntities.get(index) };
23997
- blocks.push(block);
23998
- combined.set(key, block);
23999
- }
24000
- const entities = compounds(tags.Entities, "Entities").map((entity) => {
24001
- const pos = entity.Pos;
24002
- if (pos?.type !== "list" || pos.value.type !== "double" || pos.value.value.length !== 3 || !pos.value.value.every(Number.isFinite)) {
24003
- throw new MineRenderError(`Litematica region ${name} entity Pos must contain three doubles`);
24004
- }
24005
- const absolute = pos.value.value.map((value2, axis) => value2 + position[axis]);
24006
- const value = { ...entity, Pos: { type: "list", value: { type: "double", value: [...absolute] } } };
24007
- return { position: absolute, nbt: { type: "compound", value } };
24008
- });
24009
- return { name, position, signedSize, size, blocks, entities, dataVersion };
24010
- }
24011
- function minimum(position, size) {
24012
- return position + Math.min(0, size + 1);
24013
- }
24014
- function integer2(tag, name) {
24015
- if (tag?.type !== "int" || !Number.isInteger(tag.value) || tag.value < -2147483648 || tag.value > 2147483647) {
24016
- throw new MineRenderError(`Litematica ${name} must be an int`);
24017
- }
24018
- return tag.value;
24019
- }
24020
- function compound(tag, name) {
24021
- if (tag?.type !== "compound" || !tag.value || typeof tag.value !== "object" || Array.isArray(tag.value)) {
24022
- throw new MineRenderError(`Litematica ${name} must be a compound`);
24023
- }
24024
- return tag.value;
24025
- }
24026
- function blockPosition(tags) {
24027
- return ["x", "y", "z"].map((axis) => integer2(tags[axis], axis));
24028
- }
24029
- function compounds(tag, name) {
24030
- if (tag === void 0) return [];
24031
- if (tag.type === "list" && Array.isArray(tag.value.value)) {
24032
- if (tag.value.value.length === 0) return [];
24033
- if (tag.value.type === "compound") return tag.value.value;
24034
- }
24035
- throw new MineRenderError(`Litematica ${name} must be a compound list`);
24036
- }
24037
- function blockState(tags) {
24038
- const name = tags.Name;
24039
- if (name?.type !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(name.value)) {
24040
- throw new MineRenderError("Litematica palette entry must have a block Name");
24041
- }
24042
- const properties = {};
24043
- if (tags.Properties !== void 0) {
24044
- for (const [key, tag] of Object.entries(compound(tags.Properties, "block Properties"))) {
24045
- if (tag?.type !== "string") throw new MineRenderError("Litematica block properties must be strings");
24046
- Object.defineProperty(properties, key, { value: tag.value, enumerable: true, writable: true, configurable: true });
24047
- }
24048
- }
24049
- return { type: name.value.includes(":") ? name.value : `minecraft:${name.value}`, properties };
24050
- }
24051
-
24052
- // src/model/multiblock/SchematicParser.ts
24053
- init_MineRenderError();
24054
-
24055
23904
  // src/model/multiblock/legacyBlocks.json
24056
23905
  var legacyBlocks_default = {
24057
23906
  __comment: "Block mappings from res/idsToNames.json (WorldEdit legacy.json); block names and cauldrons updated for Minecraft 1.21.11. Source: https://github.com/sk89q/WorldEdit/blob/master/worldedit-core/src/main/resources/com/sk89q/worldedit/world/registry/legacy.json",
@@ -24895,12 +24744,20 @@ var legacyBlocks_default = {
24895
24744
  "131:15": "minecraft:tripwire_hook[powered=true,attached=true,facing=east]",
24896
24745
  "132:0": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=false]",
24897
24746
  "132:1": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=false]",
24747
+ "132:2": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=false]",
24748
+ "132:3": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=false]",
24898
24749
  "132:4": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=true]",
24899
24750
  "132:5": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=true]",
24751
+ "132:6": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,west=false,attached=true]",
24752
+ "132:7": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,west=false,attached=true]",
24900
24753
  "132:8": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=false]",
24901
24754
  "132:9": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=false]",
24755
+ "132:10": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=false]",
24756
+ "132:11": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=false]",
24902
24757
  "132:12": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=true]",
24903
24758
  "132:13": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=true]",
24759
+ "132:14": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,west=false,attached=true]",
24760
+ "132:15": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,west=false,attached=true]",
24904
24761
  "133:0": "minecraft:emerald_block",
24905
24762
  "134:0": "minecraft:spruce_stairs[half=bottom,shape=outer_right,facing=east]",
24906
24763
  "134:1": "minecraft:spruce_stairs[half=bottom,shape=outer_right,facing=west]",
@@ -25741,7 +25598,175 @@ var legacyBlocks_default = {
25741
25598
  }
25742
25599
  };
25743
25600
 
25601
+ // src/model/multiblock/LegacyBlocks.ts
25602
+ function resolveLegacyBlock(id, metadata, customMappings = {}, lenient = false) {
25603
+ const mapping = legacyBlocks_default.blocks;
25604
+ const key = `${id}:${metadata}`;
25605
+ let mapped = customMappings[key] ?? mapping[key];
25606
+ if (!mapped && lenient) mapped = customMappings[`${id}:0`] ?? mapping[`${id}:0`];
25607
+ if (!mapped) return void 0;
25608
+ const [type, state] = mapped.split("[");
25609
+ const properties = {};
25610
+ if (state) {
25611
+ for (const property of state.slice(0, -1).split(",")) {
25612
+ const [key2, value] = property.split("=");
25613
+ properties[key2] = value;
25614
+ }
25615
+ }
25616
+ return { type, properties };
25617
+ }
25618
+
25619
+ // src/model/multiblock/LitematicaParser.ts
25620
+ init_MineRenderError();
25621
+ var LitematicaParser = class {
25622
+ /**
25623
+ * Parses decoded NBT, preserving subregion placement and omitting air.
25624
+ * Later entries in regions replace earlier cells, including with air.
25625
+ * Size encloses all region bounds; positions retain the schematic origin.
25626
+ * Metadata and scheduled ticks are not imported. Named regions can be placed individually.
25627
+ */
25628
+ static async parse(nbt) {
25629
+ const tags = nbt.value;
25630
+ const version2 = integer2(tags.Version, "Version");
25631
+ if (version2 < 5 || version2 > 7) throw new MineRenderError(`Unsupported Litematica version ${version2}; expected 5, 6, or 7`);
25632
+ const dataVersion = tags.MinecraftDataVersion === void 0 ? void 0 : integer2(tags.MinecraftDataVersion, "MinecraftDataVersion");
25633
+ const blocks = /* @__PURE__ */ new Map();
25634
+ const regions = [];
25635
+ const min = [Infinity, Infinity, Infinity];
25636
+ const max = [-Infinity, -Infinity, -Infinity];
25637
+ for (const [name, tag] of Object.entries(compound(tags.Regions, "Regions"))) {
25638
+ const region = readRegion(compound(tag, `region ${name}`), name, dataVersion, blocks);
25639
+ regions.push(region);
25640
+ for (let axis = 0; axis < 3; axis++) {
25641
+ const start = minimum(region.position[axis], region.signedSize[axis]);
25642
+ min[axis] = Math.min(min[axis], start);
25643
+ max[axis] = Math.max(max[axis], start + region.size[axis]);
25644
+ }
25645
+ }
25646
+ return {
25647
+ size: regions.length ? min.map((value, axis) => max[axis] - value) : [0, 0, 0],
25648
+ blocks: [...blocks.values()],
25649
+ entities: regions.flatMap((region) => region.entities ?? []),
25650
+ dataVersion,
25651
+ regions
25652
+ };
25653
+ }
25654
+ };
25655
+ function readRegion(tags, name, dataVersion, combined) {
25656
+ const position = blockPosition(compound(tags.Position, `region ${name} Position`));
25657
+ const signedSize = blockPosition(compound(tags.Size, `region ${name} Size`));
25658
+ if (signedSize.some((value) => value === 0)) throw new MineRenderError(`Litematica region ${name} dimensions must be nonzero`);
25659
+ const size = signedSize.map(Math.abs);
25660
+ const min = position.map((value, axis) => minimum(value, signedSize[axis]));
25661
+ const [width, height, length] = size;
25662
+ const volume = width * height * length;
25663
+ const palette = compounds(tags.BlockStatePalette, "BlockStatePalette").map(blockState);
25664
+ if (!palette.length) throw new MineRenderError(`Litematica region ${name} has no block palette`);
25665
+ const bits = Math.max(2, Math.ceil(Math.log2(palette.length)));
25666
+ if (!Number.isSafeInteger(volume * bits)) throw new MineRenderError(`Litematica region ${name} volume is too large`);
25667
+ const packed = tags.BlockStates;
25668
+ const expected = Math.ceil(volume * bits / 64);
25669
+ if (packed?.type !== "longArray" || !Array.isArray(packed.value) || packed.value.length !== expected) {
25670
+ throw new MineRenderError(`Litematica region ${name} BlockStates must contain ${expected} longs`);
25671
+ }
25672
+ const words = packed.value.map((pair) => {
25673
+ if (!Array.isArray(pair) || pair.length !== 2 || !pair.every((value) => Number.isInteger(value) && value >= -2147483648 && value <= 4294967295)) {
25674
+ throw new MineRenderError(`Litematica region ${name} has an invalid BlockStates long`);
25675
+ }
25676
+ return [pair[0] >>> 0, pair[1] >>> 0];
25677
+ });
25678
+ const blockEntities = /* @__PURE__ */ new Map();
25679
+ for (const tile of compounds(tags.TileEntities, "TileEntities")) {
25680
+ const pos = blockPosition(tile);
25681
+ if (pos.some((value2, axis) => value2 < 0 || value2 >= size[axis])) {
25682
+ throw new MineRenderError(`Litematica region ${name} block entity is outside its dimensions`);
25683
+ }
25684
+ const index = pos[0] + pos[2] * width + pos[1] * width * length;
25685
+ if (blockEntities.has(index)) throw new MineRenderError(`Duplicate Litematica block entity at index ${index}`);
25686
+ const value = { ...tile };
25687
+ for (const [axis, key] of ["x", "y", "z"].entries()) value[key] = { type: "int", value: pos[axis] + min[axis] };
25688
+ blockEntities.set(index, { type: "compound", value });
25689
+ }
25690
+ const blocks = [];
25691
+ const mask = 2 ** bits - 1;
25692
+ for (let index = 0; index < volume; index++) {
25693
+ const word = Math.floor(index * bits / 64);
25694
+ const shift = index * bits % 64;
25695
+ const [high, low] = words[word];
25696
+ let value = shift < 32 ? low >>> shift : high >>> shift - 32;
25697
+ if (shift < 32 && shift + bits > 32) value |= high << 32 - shift;
25698
+ if (shift + bits > 64) value |= words[word + 1][1] << 64 - shift;
25699
+ const id = (value & mask) >>> 0;
25700
+ const state = palette[id];
25701
+ if (!state) throw new MineRenderError(`Litematica region ${name} palette index ${id} does not exist`);
25702
+ const pos = [
25703
+ index % width + min[0],
25704
+ Math.floor(index / (width * length)) + min[1],
25705
+ Math.floor(index / width) % length + min[2]
25706
+ ];
25707
+ const key = pos.join(",");
25708
+ if (["minecraft:air", "minecraft:cave_air", "minecraft:void_air"].includes(state.type)) {
25709
+ combined.delete(key);
25710
+ continue;
25711
+ }
25712
+ const block = { type: state.type, properties: { ...state.properties }, position: pos, nbt: blockEntities.get(index) };
25713
+ blocks.push(block);
25714
+ combined.set(key, block);
25715
+ }
25716
+ const entities = compounds(tags.Entities, "Entities").map((entity) => {
25717
+ const pos = entity.Pos;
25718
+ if (pos?.type !== "list" || pos.value.type !== "double" || pos.value.value.length !== 3 || !pos.value.value.every(Number.isFinite)) {
25719
+ throw new MineRenderError(`Litematica region ${name} entity Pos must contain three doubles`);
25720
+ }
25721
+ const absolute = pos.value.value.map((value2, axis) => value2 + position[axis]);
25722
+ const value = { ...entity, Pos: { type: "list", value: { type: "double", value: [...absolute] } } };
25723
+ return { position: absolute, nbt: { type: "compound", value } };
25724
+ });
25725
+ return { name, position, signedSize, size, blocks, entities, dataVersion };
25726
+ }
25727
+ function minimum(position, size) {
25728
+ return position + Math.min(0, size + 1);
25729
+ }
25730
+ function integer2(tag, name) {
25731
+ if (tag?.type !== "int" || !Number.isInteger(tag.value) || tag.value < -2147483648 || tag.value > 2147483647) {
25732
+ throw new MineRenderError(`Litematica ${name} must be an int`);
25733
+ }
25734
+ return tag.value;
25735
+ }
25736
+ function compound(tag, name) {
25737
+ if (tag?.type !== "compound" || !tag.value || typeof tag.value !== "object" || Array.isArray(tag.value)) {
25738
+ throw new MineRenderError(`Litematica ${name} must be a compound`);
25739
+ }
25740
+ return tag.value;
25741
+ }
25742
+ function blockPosition(tags) {
25743
+ return ["x", "y", "z"].map((axis) => integer2(tags[axis], axis));
25744
+ }
25745
+ function compounds(tag, name) {
25746
+ if (tag === void 0) return [];
25747
+ if (tag.type === "list" && Array.isArray(tag.value.value)) {
25748
+ if (tag.value.value.length === 0) return [];
25749
+ if (tag.value.type === "compound") return tag.value.value;
25750
+ }
25751
+ throw new MineRenderError(`Litematica ${name} must be a compound list`);
25752
+ }
25753
+ function blockState(tags) {
25754
+ const name = tags.Name;
25755
+ if (name?.type !== "string" || !/^(?:[a-z0-9_.-]+:)?[a-z0-9_./-]+$/.test(name.value)) {
25756
+ throw new MineRenderError("Litematica palette entry must have a block Name");
25757
+ }
25758
+ const properties = {};
25759
+ if (tags.Properties !== void 0) {
25760
+ for (const [key, tag] of Object.entries(compound(tags.Properties, "block Properties"))) {
25761
+ if (tag?.type !== "string") throw new MineRenderError("Litematica block properties must be strings");
25762
+ Object.defineProperty(properties, key, { value: tag.value, enumerable: true, writable: true, configurable: true });
25763
+ }
25764
+ }
25765
+ return { type: name.value.includes(":") ? name.value : `minecraft:${name.value}`, properties };
25766
+ }
25767
+
25744
25768
  // src/model/multiblock/SchematicParser.ts
25769
+ init_MineRenderError();
25745
25770
  var SchematicParser = class {
25746
25771
  /**
25747
25772
  * Parses a legacy schematic using custom mappings before the bundled defaults.
@@ -25783,35 +25808,21 @@ var SchematicParser = class {
25783
25808
  tileEntities.set((y * length + z) * width + x, { type: "compound", value: tile });
25784
25809
  }
25785
25810
  const blocks = [];
25786
- const mapping = legacyBlocks_default.blocks;
25787
25811
  for (let index = 0; index < volume; index++) {
25788
25812
  const packed = addBlocks?.value[index >> 1] ?? 0;
25789
25813
  const high = packed >> (index & 1) * 4 & 15;
25790
25814
  const id = high << 8 | ids.value[index] & 255;
25791
25815
  const metadata = data.value[index] & 255;
25792
- const key = `${id}:${metadata}`;
25793
- let mapped = customMappings[key] ?? mapping[key];
25794
- if (!mapped && options.lenient) {
25795
- mapped = customMappings[`${id}:0`] ?? mapping[`${id}:0`];
25796
- }
25797
- if (!mapped) {
25816
+ const block = resolveLegacyBlock(id, metadata, customMappings, options.lenient);
25817
+ if (!block) {
25798
25818
  if (options.lenient) {
25799
25819
  continue;
25800
25820
  }
25801
25821
  throw new MineRenderError(`Unsupported legacy block ${id}:${metadata} at schematic index ${index}`);
25802
25822
  }
25803
- const [type, state] = mapped.split("[");
25804
- if (type === "minecraft:air") continue;
25805
- const properties = {};
25806
- if (state) {
25807
- for (const property of state.slice(0, -1).split(",")) {
25808
- const [key2, value] = property.split("=");
25809
- properties[key2] = value;
25810
- }
25811
- }
25823
+ if (block.type === "minecraft:air") continue;
25812
25824
  blocks.push({
25813
- type,
25814
- properties,
25825
+ ...block,
25815
25826
  position: [index % width, Math.floor(index / (width * length)), Math.floor(index / width) % length],
25816
25827
  nbt: tileEntities.get(index)
25817
25828
  });
@@ -29191,10 +29202,13 @@ var AnvilParser = class {
29191
29202
  return this.locations(this.bytes(data)).map(({ x, z }) => ({ x, z }));
29192
29203
  }
29193
29204
  /** Decodes all stored chunks. Gzip, zlib, LZ4, and uncompressed payloads are supported. */
29194
- static async parse(data) {
29205
+ static async parse(data, options = {}) {
29206
+ options.signal?.throwIfAborted();
29195
29207
  const bytes = this.bytes(data);
29196
29208
  const chunks = [];
29197
- for (const location of this.locations(bytes)) chunks.push(await this.readChunk(bytes, location));
29209
+ for (const location of this.locations(bytes)) {
29210
+ chunks.push(await this.abortable(this.readChunk(bytes, location, options), options.signal));
29211
+ }
29198
29212
  return { chunks };
29199
29213
  }
29200
29214
  /**
@@ -29203,13 +29217,28 @@ var AnvilParser = class {
29203
29217
  * @param localZ - Chunk z within the region, from 0 to 31.
29204
29218
  * @returns The chunk with absolute coordinates, or `undefined` if the region has no chunk there.
29205
29219
  */
29206
- static async parseChunk(data, localX, localZ) {
29220
+ static async parseChunk(data, localX, localZ, options = {}) {
29221
+ options.signal?.throwIfAborted();
29207
29222
  if (![localX, localZ].every((value) => Number.isInteger(value) && value >= 0 && value < 32)) {
29208
29223
  throw new RangeError("Region-local chunk coordinates must be integers from 0 to 31");
29209
29224
  }
29210
29225
  const bytes = this.bytes(data);
29211
29226
  const location = this.locations(bytes).find(({ x, z }) => x === localX && z === localZ);
29212
- return location ? this.readChunk(bytes, location) : void 0;
29227
+ return location ? this.abortable(this.readChunk(bytes, location, options), options.signal) : void 0;
29228
+ }
29229
+ static async abortable(promise, signal) {
29230
+ if (!signal) return promise;
29231
+ let abort;
29232
+ const cancelled = new Promise((_, reject) => {
29233
+ abort = () => reject(signal.reason);
29234
+ signal.addEventListener("abort", abort, { once: true });
29235
+ if (signal.aborted) abort();
29236
+ });
29237
+ try {
29238
+ return await Promise.race([promise, cancelled]);
29239
+ } finally {
29240
+ signal.removeEventListener("abort", abort);
29241
+ }
29213
29242
  }
29214
29243
  static bytes(data) {
29215
29244
  return data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -29235,20 +29264,41 @@ var AnvilParser = class {
29235
29264
  }
29236
29265
  return locations;
29237
29266
  }
29238
- static async readChunk(data, location) {
29239
- const compression = data[location.offset + 4];
29240
- if (compression & 128) throw new MineRenderError("External Anvil .mcc chunks are not supported");
29267
+ static async readChunk(data, location, options) {
29268
+ options.signal?.throwIfAborted();
29269
+ const format2 = data[location.offset + 4];
29270
+ const external = (format2 & 128) !== 0;
29271
+ const compression = format2 & 127;
29241
29272
  if (compression !== 1 && compression !== 2 && compression !== 3 && compression !== 4) {
29242
29273
  throw new MineRenderError(`Unsupported Anvil compression ${compression}`);
29243
29274
  }
29244
29275
  let payload = data.subarray(location.offset + 5, location.offset + 4 + location.length);
29276
+ let expected;
29277
+ if (external) {
29278
+ if (!options.region || !options.readExternalChunk) {
29279
+ throw new MineRenderError("External Anvil .mcc chunks require region coordinates and readExternalChunk");
29280
+ }
29281
+ if (![options.region.x, options.region.z].every(Number.isSafeInteger)) {
29282
+ throw new RangeError("Anvil region coordinates must be safe integers");
29283
+ }
29284
+ expected = { x: options.region.x * 32 + location.x, z: options.region.z * 32 + location.z };
29285
+ if (![expected.x, expected.z].every(Number.isSafeInteger)) {
29286
+ throw new RangeError("External Anvil chunk coordinates must be safe integers");
29287
+ }
29288
+ const externalData = await options.readExternalChunk(expected.x, expected.z, options.signal);
29289
+ options.signal?.throwIfAborted();
29290
+ if (externalData === void 0) throw new MineRenderError(`External Anvil chunk c.${expected.x}.${expected.z}.mcc is missing`);
29291
+ payload = this.bytes(externalData);
29292
+ }
29245
29293
  if (compression === 4) {
29246
29294
  payload = decodeAnvilLz4(payload);
29247
29295
  } else if (compression !== 3) {
29248
29296
  const stream = new Blob([payload]).stream().pipeThrough(new DecompressionStream(compression === 1 ? "gzip" : "deflate"));
29249
29297
  payload = new Uint8Array(await new Response(stream).arrayBuffer());
29250
29298
  }
29299
+ options.signal?.throwIfAborted();
29251
29300
  const nbt = await NBTHelper.fromBuffer(payload, "big");
29301
+ options.signal?.throwIfAborted();
29252
29302
  const root = nbt.value;
29253
29303
  const level = root.Level?.type === "compound" ? root.Level.value : root;
29254
29304
  const dataVersion = root.DataVersion?.type === "int" ? root.DataVersion.value : void 0;
@@ -29261,28 +29311,37 @@ var AnvilParser = class {
29261
29311
  if ((chunk.x % 32 + 32) % 32 !== location.x || (chunk.z % 32 + 32) % 32 !== location.z) {
29262
29312
  throw new MineRenderError("Anvil chunk coordinates do not match its region location");
29263
29313
  }
29314
+ if (expected && (chunk.x !== expected.x || chunk.z !== expected.z)) {
29315
+ throw new MineRenderError(`Anvil chunk coordinates do not match external file c.${expected.x}.${expected.z}.mcc`);
29316
+ }
29317
+ const numericStates = /* @__PURE__ */ new Map();
29264
29318
  for (const section of this.compounds(level.sections ?? level.Sections, "sections")) {
29265
29319
  const y = this.integer(section.Y, "section Y");
29320
+ let data2;
29266
29321
  if (section.Blocks || section.Data || section.Add) {
29267
- throw new MineRenderError("Numeric Anvil block IDs before Minecraft 1.13 are not supported");
29268
- }
29269
- if (section.block_states && section.block_states.type !== "compound") {
29270
- throw new MineRenderError("Anvil block_states must be a compound");
29271
- }
29272
- const modern = section.block_states?.type === "compound" ? section.block_states.value : void 0;
29273
- const paletteTag = modern ? modern.palette : section.Palette;
29274
- const palette = paletteTag?.type === "list" && paletteTag.value.type === "string" ? paletteTag.value.value : this.compounds(paletteTag, "block palette");
29275
- const data2 = new ChunkData();
29276
- if (palette.length) {
29277
- const states = palette.map((entry) => this.block(entry));
29278
- const indices = this.unpack(modern ? modern.data : section.BlockStates, states.length, !!modern, dataVersion);
29279
- for (let index = 0; index < 4096; index++) {
29280
- const state = states[indices[index]];
29281
- if (!state) throw new MineRenderError(`Anvil palette index ${indices[index]} is out of range`);
29282
- if (!ChunkData.isAir(state)) data2.set(index, state);
29322
+ if (section.Palette || section.BlockStates || section.block_states) {
29323
+ throw new MineRenderError(`Anvil section ${y} mixes numeric and paletted block states`);
29324
+ }
29325
+ data2 = this.numericSection(section, y, options, numericStates);
29326
+ } else {
29327
+ if (section.block_states && section.block_states.type !== "compound") {
29328
+ throw new MineRenderError("Anvil block_states must be a compound");
29329
+ }
29330
+ const modern = section.block_states?.type === "compound" ? section.block_states.value : void 0;
29331
+ const paletteTag = modern ? modern.palette : section.Palette;
29332
+ const palette = paletteTag?.type === "list" && paletteTag.value.type === "string" ? paletteTag.value.value : this.compounds(paletteTag, "block palette");
29333
+ data2 = new ChunkData();
29334
+ if (palette.length) {
29335
+ const states = palette.map((entry) => this.block(entry));
29336
+ const indices = this.unpack(modern ? modern.data : section.BlockStates, states.length, !!modern, dataVersion);
29337
+ for (let index = 0; index < 4096; index++) {
29338
+ const state = states[indices[index]];
29339
+ if (!state) throw new MineRenderError(`Anvil palette index ${indices[index]} is out of range`);
29340
+ if (!ChunkData.isAir(state)) data2.set(index, state);
29341
+ }
29342
+ } else if (modern || section.Palette || section.BlockStates) {
29343
+ throw new MineRenderError("Anvil section has block states without a palette");
29283
29344
  }
29284
- } else if (modern || section.Palette || section.BlockStates) {
29285
- throw new MineRenderError("Anvil section has block states without a palette");
29286
29345
  }
29287
29346
  if (chunk.sections.some((section2) => section2.y === y)) throw new MineRenderError(`Duplicate Anvil section Y ${y}`);
29288
29347
  chunk.sections.push({ y, data: data2 });
@@ -29310,6 +29369,33 @@ var AnvilParser = class {
29310
29369
  }
29311
29370
  return chunk;
29312
29371
  }
29372
+ static numericSection(section, y, options, states) {
29373
+ const bytes = (name, length, optional = false) => {
29374
+ const tag = section[name];
29375
+ if (!tag && optional) return void 0;
29376
+ if (tag?.type !== "byteArray" || !Array.isArray(tag.value) || tag.value.length !== length) {
29377
+ throw new MineRenderError(`Anvil section ${y} ${name} must contain ${length} bytes`);
29378
+ }
29379
+ return tag.value;
29380
+ };
29381
+ const ids = bytes("Blocks", 4096);
29382
+ const metadata = bytes("Data", 2048, true);
29383
+ const extra = bytes("Add", 2048, true);
29384
+ const data = new ChunkData();
29385
+ for (let index = 0; index < 4096; index++) {
29386
+ const shift = (index & 1) * 4;
29387
+ const id = ids[index] & 255 | ((extra?.[index >> 1] ?? 0) >> shift & 15) << 8;
29388
+ const value = (metadata?.[index >> 1] ?? 0) >> shift & 15;
29389
+ const key = id * 16 + value;
29390
+ if (!states.has(key)) states.set(key, resolveLegacyBlock(id, value, options.legacyMappings, options.lenient));
29391
+ const block = states.get(key);
29392
+ if (!block && !options.lenient) {
29393
+ throw new MineRenderError(`Unsupported legacy block ${id}:${value} at Anvil section ${y} index ${index}`);
29394
+ }
29395
+ if (block && !ChunkData.isAir(block)) data.set(index, block);
29396
+ }
29397
+ return data;
29398
+ }
29313
29399
  static compounds(tag, name) {
29314
29400
  if (!tag) return [];
29315
29401
  if (tag.type !== "list" || tag.value.type !== "compound" && tag.value.value.length !== 0) {
@@ -29392,6 +29478,7 @@ var AnvilWorldSource = class {
29392
29478
  this.cachedBytes = 0;
29393
29479
  this.maxCachedRegions = options.maxCachedRegions ?? 4;
29394
29480
  this.maxCachedBytes = options.maxCachedBytes ?? 64 * 1024 * 1024;
29481
+ this.parseOptions = { readExternalChunk: options.readExternalChunk, legacyMappings: options.legacyMappings, lenient: options.lenient };
29395
29482
  for (const [name, value] of Object.entries({ maxCachedRegions: this.maxCachedRegions, maxCachedBytes: this.maxCachedBytes })) {
29396
29483
  if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a nonnegative safe integer`);
29397
29484
  }
@@ -29405,7 +29492,11 @@ var AnvilWorldSource = class {
29405
29492
  const data = await this.getRegion(regionX, regionZ, signal);
29406
29493
  signal?.throwIfAborted();
29407
29494
  if (!data) return void 0;
29408
- const chunk = await abortable2(AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32), signal);
29495
+ const chunk = await AnvilParser.parseChunk(data, x - regionX * 32, z - regionZ * 32, {
29496
+ ...this.parseOptions,
29497
+ region: { x: regionX, z: regionZ },
29498
+ signal
29499
+ });
29409
29500
  signal?.throwIfAborted();
29410
29501
  if (chunk && (chunk.x !== x || chunk.z !== z)) {
29411
29502
  throw new MineRenderError(`Anvil chunk coordinates ${chunk.x},${chunk.z} do not match requested column ${x},${z}`);
@@ -29436,7 +29527,7 @@ var AnvilWorldSource = class {
29436
29527
  settled: false,
29437
29528
  promise: Promise.resolve().then(() => {
29438
29529
  controller.signal.throwIfAborted();
29439
- return this.readRegion(x, z, controller.signal);
29530
+ return abortable2(this.readRegion(x, z, controller.signal), controller.signal);
29440
29531
  }).then((data) => {
29441
29532
  controller.signal.throwIfAborted();
29442
29533
  let bytes = data instanceof Uint8Array ? data : data === void 0 ? void 0 : new Uint8Array(data);