diff --git a/src/main/java/baritone/utils/schematic/format/DefaultSchematicFormats.java b/src/main/java/baritone/utils/schematic/format/DefaultSchematicFormats.java index fb20164b..6cea77fe 100644 --- a/src/main/java/baritone/utils/schematic/format/DefaultSchematicFormats.java +++ b/src/main/java/baritone/utils/schematic/format/DefaultSchematicFormats.java @@ -21,6 +21,7 @@ import baritone.api.schematic.IStaticSchematic; import baritone.api.schematic.format.ISchematicFormat; import baritone.utils.schematic.format.defaults.MCEditSchematic; import baritone.utils.schematic.format.defaults.SpongeSchematic; +import baritone.utils.schematic.format.defaults.LitematicaSchematic; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import org.apache.commons.io.FilenameUtils; @@ -65,6 +66,16 @@ public enum DefaultSchematicFormats implements ISchematicFormat { throw new UnsupportedOperationException("Unsupported Version of a Sponge Schematic"); } } + }, + + /** + * The Litematica schematic specification. Commonly denoted by the ".litematic" file extension. + */ + Litematica("litematic") { + @Override + public IStaticSchematic parse(InputStream input) throws IOException { + return new LitematicaSchematic(CompressedStreamTools.readCompressed(input)); + } }; private final String extension; diff --git a/src/main/java/baritone/utils/schematic/format/defaults/LitematicaSchematic.java b/src/main/java/baritone/utils/schematic/format/defaults/LitematicaSchematic.java new file mode 100644 index 00000000..9c647a0b --- /dev/null +++ b/src/main/java/baritone/utils/schematic/format/defaults/LitematicaSchematic.java @@ -0,0 +1,217 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Baritone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.utils.schematic.format.defaults; + +import baritone.utils.schematic.StaticSchematic; +import net.minecraft.block.*; +import net.minecraft.block.properties.IProperty; +import net.minecraft.nbt.*; +import net.minecraft.util.ResourceLocation; +import net.minecraft.block.state.IBlockState; + +import org.apache.commons.lang3.Validate; +import javax.annotation.Nullable; +import java.util.*; + +/** + * @author Emerson + * @since 12/27/2020 + */ +public final class LitematicaSchematic extends StaticSchematic { + + public LitematicaSchematic(NBTTagCompound nbt) { + String regionName = (String) nbt.getCompoundTag("Regions").getKeySet().toArray()[0]; + this.x = Math.abs(nbt.getCompoundTag("Regions").getCompoundTag(regionName).getCompoundTag("Size").getInteger("x")); + this.y = Math.abs(nbt.getCompoundTag("Regions").getCompoundTag(regionName).getCompoundTag("Size").getInteger("y")); + this.z = Math.abs(nbt.getCompoundTag("Regions").getCompoundTag(regionName).getCompoundTag("Size").getInteger("z")); + this.states = new IBlockState[this.x][this.z][this.y]; + + + NBTTagList paletteTag = nbt.getCompoundTag("Regions").getCompoundTag(regionName).getTagList("BlockStatePalette",10); + // ListNBT paletteTag = nbt.getCompound("Regions").getCompound(regionName).getList("BlockStatePalette",10); + + // Create the block states array + IBlockState[] paletteBlockStates = new IBlockState[paletteTag.tagCount()]; + // For every part of the array + for (int i = 0; i propertiesMap = new HashMap<>(); + // Create a map for each state + for (int j = 0; j property = block.getBlockState().getProperty(keys[j].toString()); + if (property != null) { + blockState = setPropertyValue(blockState, property, propertiesMap.get(keys[j])); + } + } + paletteBlockStates[i] = blockState; + } + + + // BlockData is stored as an NBT long[] + int paletteSize = (int) Math.floor(log2(paletteTag.tagCount()))+1; + long litematicSize = (long) this.x*this.y*this.z; + + // In 1.12, the long array isn't exposed by the libraries so parsing has to be done manually + String rawBlockString = (nbt.getCompoundTag("Regions").getCompoundTag(regionName)).getTag("BlockStates").toString(); + rawBlockString = rawBlockString.substring(3,rawBlockString.length()-1); + String[] rawBlockArrayString = rawBlockString.split(","); + long[] rawBlockData = new long[rawBlockArrayString.length]; + for (int i = 0; i < rawBlockArrayString.length; i++) { + rawBlockData[i] = Long.parseLong(rawBlockArrayString[i].substring(0,rawBlockArrayString[i].length()-1)); + } + + + LitematicaBitArray bitArray = new LitematicaBitArray(paletteSize, litematicSize, rawBlockData); + if (paletteSize > 32) { + throw new IllegalStateException("Too many blocks in schematic to handle"); + } + + int[] serializedBlockStates = new int[(int) litematicSize]; + for (int i = 0; i> IBlockState setPropertyValue(IBlockState state, IProperty property, String value) { + Optional parsed = property.parseValue(value).toJavaUtil(); + if (parsed.isPresent()) { + return state.withProperty(property, parsed.get()); + } else { + throw new IllegalArgumentException("Invalid value for property " + property); + } + } + + /** LitematicaBitArray class from litematica */ + private static class LitematicaBitArray + { + /** The long array that is used to store the data for this BitArray. */ + private final long[] longArray; + /** Number of bits a single entry takes up */ + private final int bitsPerEntry; + /** + * The maximum value for a single entry. This also works as a bitmask for a single entry. + * For instance, if bitsPerEntry were 5, this value would be 31 (ie, {@code 0b00011111}). + */ + private final long maxEntryValue; + /** Number of entries in this array (not the length of the long array that internally backs this array) */ + private final long arraySize; + + public LitematicaBitArray(int bitsPerEntryIn, long arraySizeIn, @Nullable long[] longArrayIn) + { + Validate.inclusiveBetween(1L, 32L, (long) bitsPerEntryIn); + this.arraySize = arraySizeIn; + this.bitsPerEntry = bitsPerEntryIn; + this.maxEntryValue = (1L << bitsPerEntryIn) - 1L; + + if (longArrayIn != null) + { + this.longArray = longArrayIn; + } + else + { + this.longArray = new long[(int) (roundUp((long) arraySizeIn * (long) bitsPerEntryIn, 64L) / 64L)]; + } + } + + public void setAt(long index, int value) + { + Validate.inclusiveBetween(0L, this.arraySize - 1L, (long) index); + Validate.inclusiveBetween(0L, this.maxEntryValue, (long) value); + long startOffset = index * (long) this.bitsPerEntry; + int startArrIndex = (int) (startOffset >> 6); // startOffset / 64 + int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6); + int startBitOffset = (int) (startOffset & 0x3F); // startOffset % 64 + this.longArray[startArrIndex] = this.longArray[startArrIndex] & ~(this.maxEntryValue << startBitOffset) | ((long) value & this.maxEntryValue) << startBitOffset; + + if (startArrIndex != endArrIndex) + { + int endOffset = 64 - startBitOffset; + int j1 = this.bitsPerEntry - endOffset; + this.longArray[endArrIndex] = this.longArray[endArrIndex] >>> j1 << j1 | ((long) value & this.maxEntryValue) >> endOffset; + } + } + + public int getAt(long index) + { + Validate.inclusiveBetween(0L, this.arraySize - 1L, (long) index); + long startOffset = index * (long) this.bitsPerEntry; + int startArrIndex = (int) (startOffset >> 6); // startOffset / 64 + int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6); + int startBitOffset = (int) (startOffset & 0x3F); // startOffset % 64 + + if (startArrIndex == endArrIndex) + { + return (int) (this.longArray[startArrIndex] >>> startBitOffset & this.maxEntryValue); + } + else + { + int endOffset = 64 - startBitOffset; + return (int) ((this.longArray[startArrIndex] >>> startBitOffset | this.longArray[endArrIndex] << endOffset) & this.maxEntryValue); + } + } + + + public long size() + { + return this.arraySize; + } + + public static long roundUp(long number, long interval) + { + if (interval == 0) + { + return 0; + } + else if (number == 0) + { + return interval; + } + else + { + if (number < 0) + { + interval *= -1; + } + + long i = number % interval; + return i == 0 ? number : number + interval - i; + } + } + } +} \ No newline at end of file