Added better saving and loading methods to Schematics

Added some extra NBT utils
Added support for TileEntities and Entities to schematics
This commit is contained in:
xGamingDudex 2016-05-26 01:17:19 +02:00
parent 8a2c30fa84
commit 7a0eea7aa0
4 changed files with 588 additions and 11 deletions

View File

@ -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<String, Tag> 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<Tag> list = new ArrayList<Tag>();
Class<? extends Tag> 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<String> tags = other.c();
Map<String, Tag> map = new HashMap<String, Tag>();
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());
}
}

View File

@ -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<BlockVector, Map<String, Tag>> _tileEntities;
private final List<Tag> _entities;
public Schematic(short width, short height, short length, short[] blocks, byte[] blockData, Vector worldEditOffset, Map<BlockVector, Map<String, Tag>> tileEntities, List<Tag> 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<BlockVector, Map<String, Tag>> 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<String, Tag> 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<Tag> getEntities()
{
return _entities;
}
public Map<BlockVector, Map<String, Tag>> getTileEntities()
{
return _tileEntities;
}
@Override
public String toString()

View File

@ -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<Tag> tileEntities = getChildTag(schematic, "TileEntities", ListTag.class).getValue();
Map<BlockVector, Map<String, Tag>> 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<String, Tag> values = new HashMap<>();
for (Map.Entry<String, Tag> 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<Tag> 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<String, Tag> 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<BlockVector, Map<String, Tag>> stileEntities = schematic.getTileEntities();
byte[] blocks = new byte[sblocks.length];
byte[] addBlocks = null;
byte[] blockData = schematic.getBlockData();
List<Tag> tileEntities = new ArrayList<Tag>();
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<String, Tag> 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<Tag> 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<BlockVector, Map<String, Tag>> 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<Tag> 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);
}

View File

@ -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)
{