diff --git a/Plugins/Mineplex.Core.Common/src/com/java/sk89q/jnbt/NBTUtils.java b/Plugins/Mineplex.Core.Common/src/com/java/sk89q/jnbt/NBTUtils.java index bafd00135..5f28bea4d 100755 --- a/Plugins/Mineplex.Core.Common/src/com/java/sk89q/jnbt/NBTUtils.java +++ b/Plugins/Mineplex.Core.Common/src/com/java/sk89q/jnbt/NBTUtils.java @@ -19,7 +19,27 @@ package com.java.sk89q.jnbt; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; + +import net.minecraft.server.v1_8_R3.NBTBase; +import net.minecraft.server.v1_8_R3.NBTTagByte; +import net.minecraft.server.v1_8_R3.NBTTagByteArray; +import net.minecraft.server.v1_8_R3.NBTTagCompound; +import net.minecraft.server.v1_8_R3.NBTTagDouble; +import net.minecraft.server.v1_8_R3.NBTTagEnd; +import net.minecraft.server.v1_8_R3.NBTTagFloat; +import net.minecraft.server.v1_8_R3.NBTTagInt; +import net.minecraft.server.v1_8_R3.NBTTagIntArray; +import net.minecraft.server.v1_8_R3.NBTTagList; +import net.minecraft.server.v1_8_R3.NBTTagLong; +import net.minecraft.server.v1_8_R3.NBTTagShort; +import net.minecraft.server.v1_8_R3.NBTTagString; /** * A class which contains NBT-related utility methods. @@ -166,5 +186,207 @@ public final class NBTUtils { } return expected.cast(tag); } + + + + + public static NBTBase toNative(Tag tag) { + if (tag instanceof IntArrayTag) { + return toNative((IntArrayTag) tag); + + } else if (tag instanceof ListTag) { + return toNative((ListTag) tag); + + } else if (tag instanceof LongTag) { + return toNative((LongTag) tag); + + } else if (tag instanceof StringTag) { + return toNative((StringTag) tag); + + } else if (tag instanceof IntTag) { + return toNative((IntTag) tag); + + } else if (tag instanceof ByteTag) { + return toNative((ByteTag) tag); + + } else if (tag instanceof ByteArrayTag) { + return toNative((ByteArrayTag) tag); + + } else if (tag instanceof CompoundTag) { + return toNative((CompoundTag) tag); + + } else if (tag instanceof FloatTag) { + return toNative((FloatTag) tag); + + } else if (tag instanceof ShortTag) { + return toNative((ShortTag) tag); + + } else if (tag instanceof DoubleTag) { + return toNative((DoubleTag) tag); + } else { + throw new IllegalArgumentException("Can't convert tag of type " + tag.getClass().getCanonicalName()); + } + } + + public static NBTTagIntArray toNative(IntArrayTag tag) { + int[] value = tag.getValue(); + return new NBTTagIntArray(Arrays.copyOf(value, value.length)); + } + + public static NBTTagList toNative(ListTag tag) { + NBTTagList list = new NBTTagList(); + for (Tag child : tag.getValue()) { + if (child instanceof EndTag) { + continue; + } + list.add(toNative(child)); + } + return list; + } + + public static NBTTagLong toNative(LongTag tag) { + return new NBTTagLong(tag.getValue()); + } + + public static NBTTagString toNative(StringTag tag) { + return new NBTTagString(tag.getValue()); + } + + public static NBTTagInt toNative(IntTag tag) { + return new NBTTagInt(tag.getValue()); + } + + public static NBTTagByte toNative(ByteTag tag) { + return new NBTTagByte(tag.getValue()); + } + + public static NBTTagByteArray toNative(ByteArrayTag tag) { + byte[] value = tag.getValue(); + return new NBTTagByteArray(Arrays.copyOf(value, value.length)); + } + + public static NBTTagCompound toNative(CompoundTag tag) { + NBTTagCompound compound = new NBTTagCompound(); + for (Entry child : tag.getValue().entrySet()) { + compound.set(child.getKey(), toNative(child.getValue())); + } + return compound; + } + + public static NBTTagFloat toNative(FloatTag tag) { + return new NBTTagFloat(tag.getValue()); + } + + public static NBTTagShort toNative(ShortTag tag) { + return new NBTTagShort(tag.getValue()); + } + + public static NBTTagDouble toNative(DoubleTag tag) { + return new NBTTagDouble(tag.getValue()); + } + + public static Tag fromNative(NBTBase other) { + if (other instanceof NBTTagIntArray) { + return fromNative((NBTTagIntArray) other); + + } else if (other instanceof NBTTagList) { + return fromNative((NBTTagList) other); + + } else if (other instanceof NBTTagEnd) { + return fromNative((NBTTagEnd) other); + + } else if (other instanceof NBTTagLong) { + return fromNative((NBTTagLong) other); + + } else if (other instanceof NBTTagString) { + return fromNative((NBTTagString) other); + + } else if (other instanceof NBTTagInt) { + return fromNative((NBTTagInt) other); + + } else if (other instanceof NBTTagByte) { + return fromNative((NBTTagByte) other); + + } else if (other instanceof NBTTagByteArray) { + return fromNative((NBTTagByteArray) other); + + } else if (other instanceof NBTTagCompound) { + return fromNative((NBTTagCompound) other); + + } else if (other instanceof NBTTagFloat) { + return fromNative((NBTTagFloat) other); + + } else if (other instanceof NBTTagShort) { + return fromNative((NBTTagShort) other); + + } else if (other instanceof NBTTagDouble) { + return fromNative((NBTTagDouble) other); + } else { + throw new IllegalArgumentException("Can't convert other of type " + other.getClass().getCanonicalName()); + } + } + + public static IntArrayTag fromNative(NBTTagIntArray other) { + int[] value = other.c(); + return new IntArrayTag(Arrays.copyOf(value, value.length)); + } + + public static ListTag fromNative(NBTTagList other) { + other = (NBTTagList) other.clone(); + List list = new ArrayList(); + Class listClass = StringTag.class; + for (int i = 0; i < other.size(); i++) { + Tag child = fromNative(other.a(0)); + list.add(child); + listClass = child.getClass(); + } + return new ListTag(listClass, list); + } + + public static EndTag fromNative(NBTTagEnd other) { + return new EndTag(); + } + + public static LongTag fromNative(NBTTagLong other) { + return new LongTag(other.c()); + } + + public static StringTag fromNative(NBTTagString other) { + return new StringTag(other.a_()); + } + + public static IntTag fromNative(NBTTagInt other) { + return new IntTag(other.d()); + } + + public static ByteTag fromNative(NBTTagByte other) { + return new ByteTag(other.f()); + } + + public static ByteArrayTag fromNative(NBTTagByteArray other) { + byte[] value = other.c(); + return new ByteArrayTag(Arrays.copyOf(value, value.length)); + } + + public static CompoundTag fromNative(NBTTagCompound other) { + @SuppressWarnings("unchecked") Collection tags = other.c(); + Map map = new HashMap(); + for (String tagName : tags) { + map.put(tagName, fromNative(other.get(tagName))); + } + return new CompoundTag(map); + } + + public static FloatTag fromNative(NBTTagFloat other) { + return new FloatTag(other.h()); + } + + public static ShortTag fromNative(NBTTagShort other) { + return new ShortTag(other.e()); + } + + public static DoubleTag fromNative(NBTTagDouble other) { + return new DoubleTag(other.g()); + } } diff --git a/Plugins/Mineplex.Core.Common/src/mineplex/core/common/block/schematic/Schematic.java b/Plugins/Mineplex.Core.Common/src/mineplex/core/common/block/schematic/Schematic.java index 4545425ec..d24589cc1 100644 --- a/Plugins/Mineplex.Core.Common/src/mineplex/core/common/block/schematic/Schematic.java +++ b/Plugins/Mineplex.Core.Common/src/mineplex/core/common/block/schematic/Schematic.java @@ -1,14 +1,29 @@ package mineplex.core.common.block.schematic; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.bukkit.DyeColor; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; -import com.mysql.jdbc.Util; +import com.java.sk89q.jnbt.CompoundTag; +import com.java.sk89q.jnbt.NBTUtils; +import com.java.sk89q.jnbt.Tag; import mineplex.core.common.block.DataLocationMap; +import mineplex.core.common.util.MapUtil; import mineplex.core.common.util.UtilBlock; +import net.minecraft.server.v1_8_R3.Entity; +import net.minecraft.server.v1_8_R3.EntityTypes; +import net.minecraft.server.v1_8_R3.NBTTagCompound; +import net.minecraft.server.v1_8_R3.TileEntity; +import net.minecraft.server.v1_8_R3.WorldServer; public class Schematic { @@ -18,8 +33,11 @@ public class Schematic private final short[] _blocks; private final byte[] _blockData; private final Vector _weOffset; - - public Schematic(short width, short height, short length, short[] blocks, byte[] blockData, Vector worldEditOffset) + private final Map> _tileEntities; + private final List _entities; + + + public Schematic(short width, short height, short length, short[] blocks, byte[] blockData, Vector worldEditOffset, Map> tileEntities, List entities) { _width = width; _height = height; @@ -27,6 +45,18 @@ public class Schematic _blocks = blocks; _blockData = blockData; _weOffset = worldEditOffset; + _tileEntities = tileEntities; + _entities = entities; + } + + public Schematic(short width, short height, short length, short[] blocks, byte[] blockData, Vector worldEditOffset, Map> tileEntities) + { + this(width, height, length, blocks, blockData, worldEditOffset, tileEntities, new ArrayList<>()); + } + + public Schematic(short width, short height, short length, short[] blocks, byte[] blockData, Vector worldEditOffset) + { + this(width, height, length, blocks, blockData, worldEditOffset, new HashMap<>()); } public Schematic(short width, short height, short length, short[] blocks, byte[] blockData) @@ -34,29 +64,33 @@ public class Schematic this(width, height, length, blocks, blockData, null); } - public DataLocationMap paste(Location originLocation) + public SchematicData paste(Location originLocation) { return paste(originLocation, false); } - public DataLocationMap paste(Location originLocation, boolean ignoreAir) + public SchematicData paste(Location originLocation, boolean ignoreAir) { return paste(originLocation, ignoreAir, false); } - public DataLocationMap paste(Location originLocation, boolean ignoreAir, boolean worldEditOffset) + public SchematicData paste(Location originLocation, boolean ignoreAir, boolean worldEditOffset) { if(worldEditOffset && hasWorldEditOffset()) { originLocation = originLocation.clone().add(_weOffset); } DataLocationMap locationMap = new DataLocationMap(); + + SchematicData output = new SchematicData(locationMap, originLocation.getWorld()); int startX = originLocation.getBlockX(); int startY = originLocation.getBlockY(); int startZ = originLocation.getBlockZ(); UtilBlock.startQuickRecording(); + + WorldServer nmsWorld = ((CraftWorld)originLocation.getWorld()).getHandle(); for (int x = 0; x < _width; x++) { @@ -106,16 +140,44 @@ public class Schematic continue; } } - UtilBlock.setQuick(originLocation.getWorld(), startX + x, startY + y, startZ + z, materialId, _blockData[index]); + + BlockVector bv = new BlockVector(x,y,z); + + output.getBlocksRaw().add(bv); + + Map map = _tileEntities.get(bv); + if(map != null) + { + TileEntity te = nmsWorld.getTileEntity(MapUtil.getBlockPos(bv)); + if(te == null) continue; + CompoundTag weTag = new CompoundTag(map); + NBTTagCompound tag = NBTUtils.toNative(weTag); + te.a(tag); + + output.getTileEntitiesRaw().add(bv); + } } } } UtilBlock.stopQuickRecording(); + + for(Tag tag : _entities) + { + if(tag instanceof CompoundTag) + { + CompoundTag ctag = (CompoundTag) tag; + NBTTagCompound nmsTag = NBTUtils.toNative(ctag); + Entity nmsEntity = EntityTypes.a(nmsTag, nmsWorld); + if(nmsEntity == null) continue; + + output.getEntitiesRaw().add(nmsEntity.getBukkitEntity()); + } + } - return locationMap; + return output; } /** @@ -174,6 +236,13 @@ public class Schematic { return _weOffset != null; } + + public Vector getWorldEditOffset() + { + if(!hasWorldEditOffset()) return null; + + return _weOffset.clone(); + } public int getSize() { @@ -224,6 +293,16 @@ public class Schematic { return _blockData; } + + public List getEntities() + { + return _entities; + } + + public Map> getTileEntities() + { + return _tileEntities; + } @Override public String toString() diff --git a/Plugins/Mineplex.Core.Common/src/mineplex/core/common/block/schematic/UtilSchematic.java b/Plugins/Mineplex.Core.Common/src/mineplex/core/common/block/schematic/UtilSchematic.java index 0d3b76cfa..ddb360738 100644 --- a/Plugins/Mineplex.Core.Common/src/mineplex/core/common/block/schematic/UtilSchematic.java +++ b/Plugins/Mineplex.Core.Common/src/mineplex/core/common/block/schematic/UtilSchematic.java @@ -1,27 +1,63 @@ package mineplex.core.common.block.schematic; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftEntity; +import org.bukkit.entity.Entity; +import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; import com.java.sk89q.jnbt.ByteArrayTag; import com.java.sk89q.jnbt.CompoundTag; import com.java.sk89q.jnbt.IntTag; +import com.java.sk89q.jnbt.ListTag; import com.java.sk89q.jnbt.NBTInputStream; +import com.java.sk89q.jnbt.NBTOutputStream; +import com.java.sk89q.jnbt.NBTUtils; import com.java.sk89q.jnbt.NamedTag; import com.java.sk89q.jnbt.ShortTag; +import com.java.sk89q.jnbt.StringTag; import com.java.sk89q.jnbt.Tag; +import net.minecraft.server.v1_8_R3.BlockPosition; +import net.minecraft.server.v1_8_R3.NBTTagCompound; +import net.minecraft.server.v1_8_R3.TileEntity; +import net.minecraft.server.v1_8_R3.WorldServer; + public class UtilSchematic { + public static Schematic loadSchematic(File file) throws IOException { FileInputStream fis = new FileInputStream(file); - NBTInputStream nbtStream = new NBTInputStream(new GZIPInputStream(fis)); + return loadSchematic(fis); + } + + public static Schematic loadSchematic(byte[] bytes) throws IOException + { + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + return loadSchematic(bis); + } + + public static Schematic loadSchematic(InputStream input) throws IOException + { + NBTInputStream nbtStream = new NBTInputStream(new GZIPInputStream(input)); NamedTag rootTag = nbtStream.readNamedTag(); nbtStream.close(); @@ -35,9 +71,11 @@ public class UtilSchematic short width = getChildTag(schematic, "Width", ShortTag.class).getValue(); short height = getChildTag(schematic, "Height", ShortTag.class).getValue(); short length = getChildTag(schematic, "Length", ShortTag.class).getValue(); + + byte[] blockId = getChildTag(schematic, "Blocks", ByteArrayTag.class).getValue(); byte[] addId = new byte[0]; - short[] blocks = new short[blockId.length]; + short[] blocks = new short[blockId.length]; // Have to later combine IDs byte[] blockData = getChildTag(schematic, "Data", ByteArrayTag.class).getValue(); Vector weOffset = null; @@ -49,11 +87,14 @@ public class UtilSchematic weOffset = new Vector(x, y, z); } + // We support 4096 block IDs using the same method as vanilla Minecraft, where + // the highest 4 bits are stored in a separate byte array. if (schematic.containsKey("AddBlocks")) { addId = getChildTag(schematic, "AddBlocks", ByteArrayTag.class).getValue(); } + // Combine the AddBlocks data with the first 8-bit block ID for (int index = 0; index < blockId.length; index++) { if ((index >> 1) >= addId.length) @@ -66,9 +107,233 @@ public class UtilSchematic blocks[index] = (short) (((addId[index >> 1] & 0xF0) << 4) + (blockId[index] & 0xFF)); } } + + // Need to pull out tile entities + List tileEntities = getChildTag(schematic, "TileEntities", ListTag.class).getValue(); + Map> tileEntitiesMap = new HashMap<>(); + + for (Tag tag : tileEntities) + { + if (!(tag instanceof CompoundTag)) { + continue; + } + CompoundTag t = (CompoundTag) tag; + + int x = 0; + int y = 0; + int z = 0; + + Map values = new HashMap<>(); + + for (Map.Entry entry : t.getValue().entrySet()) + { + if (entry.getValue() instanceof IntTag) + { + if (entry.getKey().equals("x")) + { + x = ((IntTag) entry.getValue()).getValue(); + } else if (entry.getKey().equals("y")) + { + y = ((IntTag) entry.getValue()).getValue(); + } else if (entry.getKey().equals("z")) + { + z = ((IntTag) entry.getValue()).getValue(); + } + } + + values.put(entry.getKey(), entry.getValue()); + } + + BlockVector vec = new BlockVector(x, y, z); + tileEntitiesMap.put(vec, values); + } + + List entityTags = getChildTag(schematic, "Entities", ListTag.class).getValue(); - return new Schematic(width, height, length, blocks, blockData, weOffset); + return new Schematic(width, height, length, blocks, blockData, weOffset, tileEntitiesMap, entityTags); + } + + /** + * @param schematic The schematic you want to turn into bytes + * @return Returns a byte array of the schematic which may be used for saving the schematic to DB or file + */ + public static byte[] getBytes(Schematic schematic) + { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + writeBytes(schematic, output); + return output.toByteArray(); + } + + /** + * @param schematic The scheamtic you want save somewhere + * @return Writes out this schematic on byte form + */ + public static void writeBytes(Schematic schematic, OutputStream output) + { + Map map = new HashMap<>(); + + short width = schematic.getWidth(); + short height = schematic.getHeight(); + short length = schematic.getLength(); + + map.put("Width", new ShortTag(width)); + map.put("Height", new ShortTag(height)); + map.put("Length", new ShortTag(length)); + + if(schematic.hasWorldEditOffset()) + { + Vector weOffset = schematic.getWorldEditOffset(); + map.put("WEOffsetX", new IntTag(weOffset.getBlockX())); + map.put("WEOffsetY", new IntTag(weOffset.getBlockX())); + map.put("WEOffsetZ", new IntTag(weOffset.getBlockX())); + } + + map.put("Materials", new StringTag("Alpha")); + + short[] sblocks = schematic.getBlocks(); + Map> stileEntities = schematic.getTileEntities(); + + + byte[] blocks = new byte[sblocks.length]; + byte[] addBlocks = null; + byte[] blockData = schematic.getBlockData(); + List tileEntities = new ArrayList(); + + for(int x = 0; x < width; x++) + { + for(int y = 0; y < height; y++) + { + for(int z = 0; z < length; z++) + { + int index = y * width * length + z * width + x; + BlockVector bv = new BlockVector(x, y, z); + + //Save 4096 IDs in an AddBlocks section + if(sblocks[index] > 255) + { + if (addBlocks == null) { // Lazily create section + addBlocks = new byte[(blocks.length >> 1) + 1]; + } + + addBlocks[index >> 1] = (byte) (((index & 1) == 0) ? + addBlocks[index >> 1] & 0xF0 | (sblocks[index] >> 8) & 0xF + : addBlocks[index >> 1] & 0xF | ((sblocks[index] >> 8) & 0xF) << 4); + } + + blocks[index] = (byte) sblocks[index]; + + Map values = stileEntities.get(bv); + if(values != null) + { + values.put("x", new IntTag(x)); + values.put("y", new IntTag(y)); + values.put("z", new IntTag(z)); + + CompoundTag tileEntityTag = new CompoundTag(values); + tileEntities.add(tileEntityTag); + + } + } + } + } + + map.put("Blocks", new ByteArrayTag(blocks)); + map.put("Data", new ByteArrayTag(blockData)); + map.put("TileEntities", new ListTag(CompoundTag.class, tileEntities)); + + if (addBlocks != null) { + map.put("AddBlocks", new ByteArrayTag(addBlocks)); + } + + // ==================================================================== + // Entities + // ==================================================================== + + List entities = schematic.getEntities(); + + map.put("Entities", new ListTag(CompoundTag.class, entities)); + + // ==================================================================== + // Output + // ==================================================================== + + CompoundTag schematicTag = new CompoundTag(map); + + try (NBTOutputStream outputStream = new NBTOutputStream(new GZIPOutputStream(output))) + { + outputStream.writeNamedTag("Schematic", schematicTag); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + public static Schematic createSchematic(Location locA, Location locB) + { + return createSchematic(locA, locB, null); + } + + public static Schematic createSchematic(Location locA, Location locB, Vector worldEditOffset) + { + World world = locA.getWorld(); + + Vector min = Vector.getMinimum(locA.toVector(), locB.toVector()); + Vector max = Vector.getMaximum(locB.toVector(), locA.toVector()); + + short width = (short) (max.getBlockX()-min.getBlockX()); + short height = (short) (max.getBlockY()-min.getBlockY()); + short length = (short) (max.getBlockZ()-min.getBlockZ()); + + short[] blocks = new short[width*height*length]; + byte[] blocksData = new byte[blocks.length]; + + WorldServer nmsWorld = ((CraftWorld)world).getHandle(); + + Map> tileEntities = new HashMap<>(); + + for(int x = min.getBlockX(); x < max.getBlockX(); x++) + { + for(int y = min.getBlockY(); y < max.getBlockY(); y++) + { + for(int z = min.getBlockZ(); z < max.getBlockZ(); z++) + { + Block b = world.getBlockAt(x, y, z); + + int index = y * width * length + z * width + x; + + blocks[index] = (short) b.getTypeId(); + blocksData[index] = b.getData(); + + BlockPosition bp = new BlockPosition(x, y, z); + + TileEntity tileEntity = nmsWorld.getTileEntity(bp); + if(tileEntity == null) continue; + + NBTTagCompound nmsTag = new NBTTagCompound(); + tileEntity.b(nmsTag); + + CompoundTag tag = NBTUtils.fromNative(nmsTag); + tileEntities.put(new BlockVector(x, y, z), tag.getValue()); + } + } + } + + List entities = new ArrayList<>(); + for(Entity e : world.getEntities()) + { + if(e.getLocation().toVector().isInAABB(min, max)) + { + net.minecraft.server.v1_8_R3.Entity nmsEntity = ((CraftEntity)e).getHandle(); + NBTTagCompound nmsTag = new NBTTagCompound(); + nmsEntity.c(nmsTag); + CompoundTag tag = NBTUtils.fromNative(nmsTag); + entities.add(tag); + } + } + + return new Schematic(width, height, length, blocks, blocksData, worldEditOffset, tileEntities, entities); } diff --git a/Plugins/Mineplex.Core.Common/src/mineplex/core/common/util/MapUtil.java b/Plugins/Mineplex.Core.Common/src/mineplex/core/common/util/MapUtil.java index 9df45eeac..abf9fb717 100644 --- a/Plugins/Mineplex.Core.Common/src/mineplex/core/common/util/MapUtil.java +++ b/Plugins/Mineplex.Core.Common/src/mineplex/core/common/util/MapUtil.java @@ -19,6 +19,7 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.util.Vector; import net.minecraft.server.v1_8_R3.Block; import net.minecraft.server.v1_8_R3.BlockPosition; @@ -284,6 +285,16 @@ public class MapUtil return true; } + + public static BlockPosition getBlockPos(Location loc) + { + return getBlockPos(loc.toVector()); + } + + public static BlockPosition getBlockPos(Vector v) + { + return getBlockPos(v.getBlockX(), v.getBlockY(), v.getBlockZ()); + } public static BlockPosition getBlockPos(int x, int y, int z) {