diff --git a/src/api/java/baritone/api/schematic/AbstractSchematic.java b/src/api/java/baritone/api/schematic/AbstractSchematic.java index 3cd14747..cca6bc96 100644 --- a/src/api/java/baritone/api/schematic/AbstractSchematic.java +++ b/src/api/java/baritone/api/schematic/AbstractSchematic.java @@ -23,6 +23,10 @@ public abstract class AbstractSchematic implements ISchematic { protected int y; protected int z; + public AbstractSchematic() { + this(0, 0, 0); + } + public AbstractSchematic(int x, int y, int z) { this.x = x; this.y = y; diff --git a/src/api/java/baritone/api/utils/BlockOptionalMeta.java b/src/api/java/baritone/api/utils/BlockOptionalMeta.java index 3ab76dd2..451d95eb 100644 --- a/src/api/java/baritone/api/utils/BlockOptionalMeta.java +++ b/src/api/java/baritone/api/utils/BlockOptionalMeta.java @@ -178,12 +178,12 @@ public final class BlockOptionalMeta { normalizations = Collections.unmodifiableMap(_normalizations); } - private static , P extends IProperty> P castToIProperty(Object value) { + public static , P extends IProperty> P castToIProperty(Object value) { //noinspection unchecked return (P) value; } - private static , P extends IProperty> C castToIPropertyValue(P iproperty, Object value) { + public static , P extends IProperty> C castToIPropertyValue(P iproperty, Object value) { //noinspection unchecked return (C) value; } @@ -191,6 +191,10 @@ public final class BlockOptionalMeta { public static IBlockState normalize(IBlockState state) { IBlockState newState = state; + // TODO: Can the state not be normalized by simply doing...? + // return state.getBlock().getDefaultState(); + // ??? + for (IProperty property : state.getProperties().keySet()) { Class valueClass = property.getValueClass(); if (normalizations.containsKey(property)) { diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java index 4d66a764..1dfd6c82 100644 --- a/src/main/java/baritone/process/BuilderProcess.java +++ b/src/main/java/baritone/process/BuilderProcess.java @@ -39,7 +39,7 @@ import baritone.utils.BlockStateInterface; import baritone.utils.PathingCommandContext; import baritone.utils.schematic.FillSchematic; import baritone.utils.schematic.MapArtSchematic; -import baritone.utils.schematic.Schematic; +import baritone.utils.schematic.format.SchematicFormat; import baritone.utils.schematic.schematica.SchematicaHelper; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import net.minecraft.block.BlockAir; @@ -48,15 +48,12 @@ import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompressedStreamTools; -import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumFacing; import net.minecraft.util.Tuple; import net.minecraft.util.math.*; import java.io.File; import java.io.FileInputStream; -import java.io.IOException; import java.util.*; import static baritone.api.pathing.movement.ActionCosts.COST_INF; @@ -118,18 +115,24 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil @Override public boolean build(String name, File schematic, Vec3i origin) { - NBTTagCompound tag; - try (FileInputStream fileIn = new FileInputStream(schematic)) { - tag = CompressedStreamTools.readCompressed(fileIn); - } catch (IOException e) { + Optional format = SchematicFormat.getByFile(schematic); + if (!format.isPresent()) { + return false; + } + + ISchematic parsed; + try { + parsed = format.get().getParser().parse(new FileInputStream(schematic)); + } catch (Exception e) { e.printStackTrace(); return false; } - //noinspection ConstantConditions - if (tag == null) { - return false; + + if (Baritone.settings().mapArtMode.value) { + parsed = new MapArtSchematic(parsed); } - build(name, parse(tag), origin); + + build(name, parsed, origin); return true; } @@ -160,10 +163,6 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil return new ArrayList<>(approxPlaceable); } - private static ISchematic parse(NBTTagCompound schematic) { - return Baritone.settings().mapArtMode.value ? new MapArtSchematic(schematic) : new Schematic(schematic); - } - @Override public boolean isActive() { return schematic != null; diff --git a/src/main/java/baritone/utils/schematic/MapArtSchematic.java b/src/main/java/baritone/utils/schematic/MapArtSchematic.java index 32b3292c..33ec3a15 100644 --- a/src/main/java/baritone/utils/schematic/MapArtSchematic.java +++ b/src/main/java/baritone/utils/schematic/MapArtSchematic.java @@ -17,24 +17,29 @@ package baritone.utils.schematic; +import baritone.api.schematic.AbstractSchematic; +import baritone.api.schematic.ISchematic; import net.minecraft.block.BlockAir; import net.minecraft.block.state.IBlockState; -import net.minecraft.nbt.NBTTagCompound; +import java.util.List; import java.util.OptionalInt; import java.util.function.Predicate; -public class MapArtSchematic extends Schematic { +public class MapArtSchematic extends AbstractSchematic { + private final ISchematic child; private final int[][] heightMap; - public MapArtSchematic(NBTTagCompound schematic) { - super(schematic); - heightMap = new int[widthX][lengthZ]; + public MapArtSchematic(ISchematic schematic) { + super(schematic.widthX(), schematic.heightY(), schematic.lengthZ()); + this.child = schematic; - for (int x = 0; x < widthX; x++) { - for (int z = 0; z < lengthZ; z++) { - IBlockState[] column = states[x][z]; + heightMap = new int[schematic.widthX()][schematic.lengthZ()]; + + for (int x = 0; x < schematic.widthX(); x++) { + for (int z = 0; z < schematic.lengthZ(); z++) { + IBlockState[] column = /*states[x][z]*/null; OptionalInt lowestBlockY = lastIndexMatching(column, state -> !(state.getBlock() instanceof BlockAir)); if (lowestBlockY.isPresent()) { @@ -44,7 +49,6 @@ public class MapArtSchematic extends Schematic { System.out.println("Letting it be whatever"); heightMap[x][z] = 256; } - } } } @@ -63,4 +67,24 @@ public class MapArtSchematic extends Schematic { // in map art, we only care about coordinates in or above the art return super.inSchematic(x, y, z, currentState) && y >= heightMap[x][z]; } + + @Override + public IBlockState desiredState(int x, int y, int z, IBlockState current, List approxPlaceable) { + return this.child.desiredState(x, y, z, current, approxPlaceable); + } + + @Override + public int widthX() { + return this.child.widthX(); + } + + @Override + public int heightY() { + return this.child.heightY(); + } + + @Override + public int lengthZ() { + return this.child.lengthZ(); + } } diff --git a/src/main/java/baritone/utils/schematic/Schematic.java b/src/main/java/baritone/utils/schematic/Schematic.java deleted file mode 100644 index 1169578a..00000000 --- a/src/main/java/baritone/utils/schematic/Schematic.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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; - -import baritone.api.schematic.ISchematic; -import net.minecraft.block.Block; -import net.minecraft.block.state.IBlockState; -import net.minecraft.nbt.NBTTagCompound; - -import java.util.List; - -public class Schematic implements ISchematic { - - public final int widthX; - public final int heightY; - public final int lengthZ; - protected final IBlockState[][][] states; - - public Schematic(NBTTagCompound schematic) { - String type = schematic.getString("Materials"); - if (!type.equals("Alpha")) { - throw new IllegalStateException("bad schematic " + type); - } - widthX = schematic.getInteger("Width"); - heightY = schematic.getInteger("Height"); - lengthZ = schematic.getInteger("Length"); - byte[] blocks = schematic.getByteArray("Blocks"); - byte[] metadata = schematic.getByteArray("Data"); - - byte[] additional = null; - if (schematic.hasKey("AddBlocks")) { - byte[] addBlocks = schematic.getByteArray("AddBlocks"); - additional = new byte[addBlocks.length * 2]; - for (int i = 0; i < addBlocks.length; i++) { - additional[i * 2 + 0] = (byte) ((addBlocks[i] >> 4) & 0xF); // lower nibble - additional[i * 2 + 1] = (byte) ((addBlocks[i] >> 0) & 0xF); // upper nibble - } - } - states = new IBlockState[widthX][lengthZ][heightY]; - for (int y = 0; y < heightY; y++) { - for (int z = 0; z < lengthZ; z++) { - for (int x = 0; x < widthX; x++) { - int blockInd = (y * lengthZ + z) * widthX + x; - - int blockID = blocks[blockInd] & 0xFF; - if (additional != null) { - // additional is 0 through 15 inclusive since it's & 0xF above - blockID |= additional[blockInd] << 8; - } - Block block = Block.REGISTRY.getObjectById(blockID); - int meta = metadata[blockInd] & 0xFF; - states[x][z][y] = block.getStateFromMeta(meta); - } - } - } - } - - @Override - public IBlockState desiredState(int x, int y, int z, IBlockState current, List approxPlaceable) { - return states[x][z][y]; - } - - @Override - public int widthX() { - return widthX; - } - - @Override - public int heightY() { - return heightY; - } - - @Override - public int lengthZ() { - return lengthZ; - } -} diff --git a/src/main/java/baritone/utils/schematic/format/SchematicFormat.java b/src/main/java/baritone/utils/schematic/format/SchematicFormat.java new file mode 100644 index 00000000..5f103d9a --- /dev/null +++ b/src/main/java/baritone/utils/schematic/format/SchematicFormat.java @@ -0,0 +1,70 @@ +/* + * 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; + +import baritone.utils.schematic.parse.ISchematicParser; +import baritone.utils.schematic.parse.MCEditParser; +import baritone.utils.schematic.parse.SpongeParser; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * @author Brady + * @since 12/13/2019 + */ +public enum SchematicFormat { + + /** + * The MCEdit schematic specification. Commonly denoted by the ".schematic" file extension. + */ + MCEDIT("schematic", MCEditParser.INSTANCE), + + /** + * The SpongePowered Schematic Specification. Commonly denoted by the ".schem" file extension. + * + * @see Sponge Schematic Specification + */ + SPONGE("schem", SpongeParser.INSTANCE); + + private final String extension; + private final ISchematicParser parser; + + SchematicFormat(String extension, ISchematicParser parser) { + this.extension = extension; + this.parser = parser; + } + + public final ISchematicParser getParser() { + return this.parser; + } + + public static Optional getByFile(File schematic) { + // TODO: Better identification + // Maybe peek file contents and make a safe determination? + return getByExtension(FilenameUtils.getExtension(schematic.getAbsolutePath())); + } + + public static Optional getByExtension(String extension) { + return extension == null || extension.isEmpty() + ? Optional.empty() + : Stream.of(values()).filter(format -> format.extension.equals(extension)).findFirst(); + } +} diff --git a/src/main/java/baritone/utils/schematic/parse/ISchematicParser.java b/src/main/java/baritone/utils/schematic/parse/ISchematicParser.java new file mode 100644 index 00000000..040addc1 --- /dev/null +++ b/src/main/java/baritone/utils/schematic/parse/ISchematicParser.java @@ -0,0 +1,32 @@ +/* + * 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.parse; + +import baritone.api.schematic.ISchematic; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Brady + * @since 12/13/2019 + */ +public interface ISchematicParser { + + ISchematic parse(InputStream input) throws IOException; +} diff --git a/src/main/java/baritone/utils/schematic/parse/MCEditParser.java b/src/main/java/baritone/utils/schematic/parse/MCEditParser.java new file mode 100644 index 00000000..dce2c856 --- /dev/null +++ b/src/main/java/baritone/utils/schematic/parse/MCEditParser.java @@ -0,0 +1,94 @@ +/* + * 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.parse; + +import baritone.api.schematic.AbstractSchematic; +import baritone.api.schematic.ISchematic; +import baritone.utils.schematic.format.SchematicFormat; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +/** + * An implementation of {@link ISchematicParser} for {@link SchematicFormat#MCEDIT} + * + * @author Brady + * @since 12/16/2019 + */ +public enum MCEditParser implements ISchematicParser { + INSTANCE; + + @Override + public ISchematic parse(InputStream input) throws IOException { + return new MCEditSchematic(CompressedStreamTools.readCompressed(input)); + } + + private static final class MCEditSchematic extends AbstractSchematic { + + private final IBlockState[][][] states; + + MCEditSchematic(NBTTagCompound schematic) { + String type = schematic.getString("Materials"); + if (!type.equals("Alpha")) { + throw new IllegalStateException("bad schematic " + type); + } + this.x = schematic.getInteger("Width"); + this.y = schematic.getInteger("Height"); + this.z = schematic.getInteger("Length"); + byte[] blocks = schematic.getByteArray("Blocks"); + byte[] metadata = schematic.getByteArray("Data"); + + byte[] additional = null; + if (schematic.hasKey("AddBlocks")) { + byte[] addBlocks = schematic.getByteArray("AddBlocks"); + additional = new byte[addBlocks.length * 2]; + for (int i = 0; i < addBlocks.length; i++) { + additional[i * 2 + 0] = (byte) ((addBlocks[i] >> 4) & 0xF); // lower nibble + additional[i * 2 + 1] = (byte) ((addBlocks[i] >> 0) & 0xF); // upper nibble + } + } + this.states = new IBlockState[this.x][this.z][this.y]; + for (int y = 0; y < this.y; y++) { + for (int z = 0; z < this.z; z++) { + for (int x = 0; x < this.x; x++) { + int blockInd = (y * this.z + z) * this.x + x; + + int blockID = blocks[blockInd] & 0xFF; + if (additional != null) { + // additional is 0 through 15 inclusive since it's & 0xF above + blockID |= additional[blockInd] << 8; + } + Block block = Block.REGISTRY.getObjectById(blockID); + int meta = metadata[blockInd] & 0xFF; + this.states[x][z][y] = block.getStateFromMeta(meta); + } + } + } + } + + @Override + public final IBlockState desiredState(int x, int y, int z, IBlockState current, List approxPlaceable) { + return this.states[x][z][y]; + } + } +} diff --git a/src/main/java/baritone/utils/schematic/parse/SpongeParser.java b/src/main/java/baritone/utils/schematic/parse/SpongeParser.java new file mode 100644 index 00000000..71984f91 --- /dev/null +++ b/src/main/java/baritone/utils/schematic/parse/SpongeParser.java @@ -0,0 +1,202 @@ +/* + * 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.parse; + +import baritone.api.schematic.AbstractSchematic; +import baritone.api.schematic.ISchematic; +import baritone.api.utils.BlockOptionalMeta; +import baritone.utils.schematic.format.SchematicFormat; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import net.minecraft.block.Block; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.ResourceLocation; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An implementation of {@link ISchematicParser} for {@link SchematicFormat#SPONGE} + * + * @author Brady + * @since 12/16/2019 + */ +public enum SpongeParser implements ISchematicParser { + INSTANCE; + + @Override + public ISchematic parse(InputStream input) throws IOException { + NBTTagCompound nbt = CompressedStreamTools.readCompressed(input); + int version = nbt.getInteger("Version"); + switch (version) { + case 1: + case 2: + return new SpongeSchematic(nbt); + default: + throw new UnsupportedOperationException("Unsupported Version of the a Sponge Schematic"); + } + } + + /** + * Implementation of the Sponge Schematic Format supporting both V1 and V2. (For the current + * use case, there is no difference between reading a V1 and V2 schematic). + */ + private static final class SpongeSchematic extends AbstractSchematic { + + /** + * Block states for this schematic stored in [x, z, y] indexing order + */ + private IBlockState[][][] states; + + SpongeSchematic(NBTTagCompound nbt) { + this.x = nbt.getInteger("Width"); + this.y = nbt.getInteger("Height"); + this.z = nbt.getInteger("Length"); + this.states = new IBlockState[this.x][this.z][this.y]; + + Int2ObjectArrayMap palette = new Int2ObjectArrayMap<>(); + NBTTagCompound paletteTag = nbt.getCompoundTag("Palette"); + for (String tag : paletteTag.getKeySet()) { + int index = paletteTag.getInteger(tag); + + SerializedBlockState serializedState = SerializedBlockState.getFromString(tag); + if (serializedState == null) { + throw new IllegalArgumentException("Unable to parse palette tag"); + } + + IBlockState state = serializedState.deserialize(); + if (state == null) { + throw new IllegalArgumentException("Unable to deserialize palette tag"); + } + + palette.put(index, state); + } + + // BlockData is stored as an NBT byte[], however, the actual data that is represented is a varint[]. + // This is kind of a hacky approach but it works /shrug + byte[] rawBlockData = nbt.getByteArray("BlockData"); + int[] blockData = new int[this.x * this.y * this.z]; + PacketBuffer buffer = new PacketBuffer(Unpooled.wrappedBuffer(rawBlockData)); + for (int i = 0; i < blockData.length; i++) { + if (buffer.readableBytes() > 0) { + blockData[i] = buffer.readVarInt(); + } else { + throw new IllegalArgumentException("Not enough"); + } + } + + for (int y = 0; y < this.y; y++) { + for (int z = 0; z < this.z; z++) { + for (int x = 0; x < this.x; x++) { + int index = (y * this.z + z) * this.x + x; + this.states[x][z][y] = palette.get(blockData[index]); + } + } + } + } + + @Override + public IBlockState desiredState(int x, int y, int z, IBlockState current, List approxPlaceable) { + return this.states[x][z][y]; + } + } + + private static final class SerializedBlockState { + + private static final Pattern REGEX = Pattern.compile("(?(\\w+:)?\\w+)(\\[(?(\\w+=\\w+,?)+)])?"); + + private final ResourceLocation resourceLocation; + private final Map properties; + private IBlockState blockState; + + private SerializedBlockState(ResourceLocation resourceLocation, Map properties) { + this.resourceLocation = resourceLocation; + this.properties = properties; + } + + ResourceLocation getResourceLocation() { + return this.resourceLocation; + } + + Map getProperties() { + return this.properties; + } + + IBlockState deserialize() { + if (this.blockState == null) { + // Get the base state for the block specified + this.blockState = Block.REGISTRY.getObject(this.resourceLocation).getDefaultState(); + + // AFAIK it is best to order the property keys so that Minecraft caches the Block States ideally + this.properties.keySet().stream().sorted(String::compareTo).forEachOrdered(key -> { + // getProperty(String) when lol + IProperty property = this.blockState.getPropertyKeys().stream() + .filter(p -> p.getName().equals(key)) + .findFirst().orElseThrow(IllegalArgumentException::new); + + Optional value = property.parseValue(this.properties.get(key)).toJavaUtil(); + if (value.isPresent()) { + this.blockState = this.blockState.withProperty( + BlockOptionalMeta.castToIProperty(property), + BlockOptionalMeta.castToIPropertyValue(property, value) + ); + } else { + throw new IllegalArgumentException(); + } + }); + } + return this.blockState; + } + + static SerializedBlockState getFromString(String s) { + Matcher m = REGEX.matcher(s); + if (!m.matches()) { + return null; + } + + try { + String location = m.group("location"); + String properties = m.group("properties"); + + ResourceLocation resourceLocation = new ResourceLocation(location); + Map propertiesMap = new HashMap<>(); + if (properties != null) { + for (String property : properties.split(",")) { + String[] split = property.split("="); + propertiesMap.put(split[0], split[1]); + } + } + + return new SerializedBlockState(resourceLocation, propertiesMap); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + } +}