diff --git a/NBTUtils/NBTUtils.iml b/NBTUtils/NBTUtils.iml
new file mode 100644
index 0000000..f3f9362
--- /dev/null
+++ b/NBTUtils/NBTUtils.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NBTUtils/src/main/Utils/BytesUtils.java b/NBTUtils/src/main/Utils/BytesUtils.java
new file mode 100644
index 0000000..8ee79ce
--- /dev/null
+++ b/NBTUtils/src/main/Utils/BytesUtils.java
@@ -0,0 +1,240 @@
+package main.Utils;
+
+import com.sun.jdi.ByteValue;
+
+import java.util.BitSet;
+
+public class BytesUtils {
+
+ public static String bytes2fStr(byte[] data) {
+ String result = "";
+ for (int i = 0; i < data.length; i++) {
+ result += Integer.toHexString((data[i] & 0xFF) | 0x100).toUpperCase().substring(1, 3) + " ";
+ }
+ return result;
+ }
+
+ public static short bytes2short(byte[] res) {
+ short ret = 0;
+ for (int i = 0; i < 2; i++) {
+ if (i < res.length)
+ ret |= (short) (res[i] & 0xFF) << (i * 8);
+ }
+ return ret;
+ }
+
+ public static int bytes2int(byte[] res) {
+ int ret = 0;
+ for (int i = 0; i < 4; i++) {
+ if (i < res.length)
+ ret |= (res[i] & 0xFF) << (i * 8);
+ }
+ return ret;
+ }
+
+ public static long bytes2long(byte[] res) {
+ long ret = 0;
+ for (int i = 0; i < 8; i++) {
+ if (i < res.length)
+ ret |= (long) (res[i] & 0xFF) << (i * 8);
+ }
+ return ret;
+ }
+
+ public static float bytes2float(byte[] res) {
+ int ret = 0;
+ for (int i = 0; i < 4; i++) {
+ ret |= (res[i] & 0xFF) << (i * 8);
+ }
+ return Float.intBitsToFloat(ret);
+ }
+
+ public static double bytes2double(byte[] res) {
+ long ret = 0;
+ for (int i = 0; i < 8; i++) {
+ ret |= (long) (res[i] & 0xFF) << (i * 8);
+ }
+ return Double.longBitsToDouble(ret);
+ }
+
+ public static byte byte2byteA(byte res) {
+ BitSet ret = new BitSet(8);
+ BitSet resb = BitSet.valueOf(new byte[]{res});
+ for (int i = 0; i < 8; i++) {
+ ret.set(i, resb.get(7 - i));
+ }
+ if (ret.length() == 0)
+ return -128;
+ return ret.toByteArray()[0];
+ }
+
+ public static byte[] bytes2bytesA(byte[] res) {
+ byte[] ret = new byte[res.length];
+ for (int i = 0; i < res.length; i++) {
+ ret[i] = byte2byteA(res[i]);
+ }
+ return ret;
+ }
+
+ public static short bytes2shortA(byte[] res) {
+ short ret = 0;
+ for (int i = 0; i < 2; i++) {
+ ret |= (short) (res[1 - i] & 0xFF) << (i * 8);
+ }
+ return ret;
+ }
+
+ public static byte[] bytesCut(byte[] res, int pos,int destPos, int destLen) {
+ byte[] ret = new byte[destLen];
+ System.arraycopy(res, pos, ret, destPos, destLen);
+ return ret;
+ }
+
+ public static byte[] bytesSplicing(byte[] a, byte[] b) {
+ byte[] ret = new byte[a.length + b.length];
+ System.arraycopy(a, 0, ret, 0, a.length);
+ System.arraycopy(b, 0, ret, a.length, b.length);
+ return ret;
+ }
+
+ public static int bytes2intA(byte[] res) {
+ int ret = 0;
+ for (int i = 0; i < 4; i++) {
+ if (3 - i < res.length)
+ ret |= (res[3 - i] & 0xFF) << (i * 8);
+ }
+ return ret;
+ }
+
+ public static long bytes2longA(byte[] res) {
+ long ret = 0;
+ for (int i = 0; i < 8; i++) {
+ if (7 - i < res.length)
+ ret |= (long) (res[7 - i] & 0xFF) << (i * 8);
+ }
+ return ret;
+ }
+
+ public static float bytes2floatA(byte[] res) {
+ int ret = 0;
+ for (int i = 0; i < 4; i++) {
+ ret |= (res[3 - i] & 0xFF) << (i * 8);
+ }
+ return Float.intBitsToFloat(ret);
+ }
+
+ public static double bytes2doubleA(byte[] res) {
+ long ret = 0;
+ for (int i = 0; i < 8; i++) {
+ ret |= (long) (res[7 - i] & 0xFF) << (i * 8);
+ }
+ return Double.longBitsToDouble(ret);
+ }
+
+
+ public static byte[] longs2bytesA(long[] res) {
+ byte[] ret = new byte[res.length * 8];
+
+ for (int i = 0; i < res.length; i++) {
+
+ for (int j = 0; j < 8; j++) {//字节
+ ret[j + i * 8] = long2bytes(res[i])[7 - j];
+ }
+
+ }
+
+ return ret;
+ }
+
+
+ public static byte[] short2bytes(short res) {
+ byte[] ret = new byte[2];
+ for (int i = 0; i < 2; i++) {
+ int offset = 16 - (i + 1) * 8;
+ ret[i] = (byte) ((res >> offset) & 0xff);
+ }
+ return ret;
+ }
+
+ public static byte[] int2bytes(int res) {
+ byte[] ret = new byte[4];
+ for (int i = 0; i < 4; i++) {
+ int offset = 32 - (i + 1) * 8;
+ ret[i] = (byte) ((res >> offset) & 0xff);
+ }
+ return ret;
+ }
+
+ public static byte[] long2bytes(long res) {
+ byte[] ret = new byte[8];
+ for (int i = 0; i < 8; i++) {
+ int offset = 64 - (i + 1) * 8;
+ ret[i] = (byte) ((res >> offset) & 0xff);
+ }
+ return ret;
+ }
+
+ public static byte[] float2bytes(float res) {
+ return int2bytes(Float.floatToIntBits(res));
+ }
+
+ public static byte[] short2bytesA(short res) {
+ byte[] buffer = new byte[2];
+ for (int i = 0; i < 2; i++) {
+ int offset = 16 - (1 - i + 1) * 8;
+ buffer[i] = (byte) ((res >> offset) & 0xff);
+ }
+ return buffer;
+ }
+
+ public static byte[] int2bytesA(int res) {
+ byte[] buffer = new byte[4];
+ for (int i = 0; i < 4; i++) {
+ int offset = 32 - (3 - i + 1) * 8;
+ buffer[i] = (byte) ((res >> offset) & 0xff);
+ }
+ return buffer;
+ }
+
+ public static byte[] long2bytesA(long res) {
+ byte[] buffer = new byte[8];
+ for (int i = 0; i < 8; i++) {
+ int offset = 64 - (7 - i + 1) * 8;
+ buffer[i] = (byte) ((res >> offset) & 0xff);
+ }
+ return buffer;
+ }
+
+ public static byte[] double2bytes(double res) {
+ return long2bytes(Double.doubleToLongBits(res));
+ }
+
+ public static byte[] float2bytesA(float res) {
+ return int2bytesA(Float.floatToIntBits(res));
+ }
+
+ public static byte[] double2bytesA(double res) {
+ return long2bytesA(Double.doubleToLongBits(res));
+ }
+
+
+ public static BitSet bytes2bits(byte[] res) {
+ BitSet ret = new BitSet(res.length * 8);
+ for (int i = 0; i < res.length; i++) {
+ for (int j = 0; j < 8; j++) {
+ ret.set(i * 8 + j, ((res[i] >> j) & 0x01) == 1);
+ }
+ }
+ return ret;
+ }
+
+ public static BitSet bytes2bitsA(byte[] res) {
+ BitSet ret = new BitSet(res.length * 8);
+ for (int i = 0; i < res.length; i++) {
+ for (int j = 0; j < 8; j++) {
+ ret.set(res.length * 8 - i * 8 - j - 1, ((res[i] >> j) & 0x01) == 1);
+ }
+ }
+ return ret;
+ }
+}
diff --git a/NBTUtils/src/main/Utils/CompressUtils.java b/NBTUtils/src/main/Utils/CompressUtils.java
new file mode 100644
index 0000000..1fec901
--- /dev/null
+++ b/NBTUtils/src/main/Utils/CompressUtils.java
@@ -0,0 +1,66 @@
+package main.Utils;
+
+import java.io.*;
+import java.util.zip.*;
+
+public abstract class CompressUtils {
+ //Zlib压缩
+ public static byte[] zlibCompress(byte[] data) throws IOException {
+ byte[] output = new byte[0];
+
+ Deflater compresser = new Deflater();
+
+ compresser.reset();
+ compresser.setInput(data);
+ compresser.finish();
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
+ byte[] buf = new byte[1024];
+ while (!compresser.finished()) {
+ int i = compresser.deflate(buf);
+ bos.write(buf, 0, i);
+ }
+ output = bos.toByteArray();
+ bos.close();
+ compresser.end();
+ return output;
+ }
+
+ //Zlib解压
+ public static byte[] zlibDecompress(byte[] data) throws IOException, DataFormatException {
+
+
+ Inflater decompresser = new Inflater();
+ decompresser.reset();
+ decompresser.setInput(data);
+
+ ByteArrayOutputStream o = new ByteArrayOutputStream(data.length);
+
+ byte[] buf = new byte[1024];
+ while (!decompresser.finished()) {
+ int i = decompresser.inflate(buf);
+ o.write(buf, 0, i);
+ }
+ byte[] output = o.toByteArray();
+ o.close();
+ decompresser.end();
+ return output;
+ }
+
+ //Gzip压缩
+ public static byte[] gzipCompress(byte[] data) throws IOException {
+ ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
+ GZIPOutputStream gzipOutputStream =new GZIPOutputStream(byteArrayOutputStream);
+ gzipOutputStream.write(data);
+ gzipOutputStream.close();
+ return byteArrayOutputStream.toByteArray();
+ }
+
+ //Gzip解压
+ public static byte[] gzipDecompress(byte[] data) throws IOException {
+ GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(data));
+ byte[] output = gzipInputStream.readAllBytes();
+ gzipInputStream.close();
+ return output;
+ }
+
+}
\ No newline at end of file
diff --git a/NBTUtils/src/main/exception/NBTException.java b/NBTUtils/src/main/exception/NBTException.java
new file mode 100644
index 0000000..55d8357
--- /dev/null
+++ b/NBTUtils/src/main/exception/NBTException.java
@@ -0,0 +1,16 @@
+package main.exception;
+
+public class NBTException extends RuntimeException {
+ public NBTException(String message) {
+ super(message);
+ System.out.println(message);
+ throw new RuntimeException();
+ }
+
+
+ @Override
+ public synchronized Throwable fillInStackTrace() {
+ // fast valid
+ return null;
+ }
+}
diff --git a/NBTUtils/src/main/io/MCUtil.java b/NBTUtils/src/main/io/MCUtil.java
new file mode 100644
index 0000000..cebec16
--- /dev/null
+++ b/NBTUtils/src/main/io/MCUtil.java
@@ -0,0 +1,531 @@
+package main.io;
+
+import main.Utils.BytesUtils;
+import main.Utils.CompressUtils;
+import main.exception.NBTException;
+import main.mc.MCA;
+import main.mc.MCPosInt;
+import main.nbt.CompoundTag;
+import main.nbt.ListTag;
+import main.nbt.TagType;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.DataFormatException;
+
+public class MCUtil {
+ /**
+ * 解析NBT
+ *
+ * @param nbt NBT数据
+ * @return 复合标签
+ * @throws IOException
+ */
+ public static CompoundTag parseNBT(byte[] nbt) throws IOException {
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(nbt);
+ CompoundTag compoundTag = parseCompoundTag(byteArrayInputStream);
+ byteArrayInputStream.close();
+ return compoundTag;
+ }
+
+
+ /**
+ * 计算方块列表大小所占存储的位大小
+ *
+ * @param blockListSize 方块列表大小
+ * @return 存储的位大小
+ */
+ public static int getMapBitSize(int blockListSize) {
+ blockListSize--;
+ int count = 0;
+ while (blockListSize != 0) {
+ count++;
+ blockListSize >>= 1;
+ }
+ return (count < 4 ? 4 : count);
+ }
+
+ /**
+ * 解析复合标签
+ *
+ * @param in 输入流
+ * @return 复合标签
+ * @throws IOException
+ */
+ private static CompoundTag parseCompoundTag(InputStream in) throws IOException {
+ CompoundTag nbt = new CompoundTag();
+
+
+ while (true) {
+ //读取标签类型
+ short tagType = BytesUtils.bytes2short(in.readNBytes(1));
+ //判断是否结束
+ if (tagType == TagType.TAG_End) {
+ return nbt;
+ }
+
+ //读取标签名
+ String tagName = new String(in.readNBytes(BytesUtils.bytes2shortA(in.readNBytes(2))));
+
+ if (tagType == TagType.TAG_Byte) {
+ nbt.setTag(tagName, in.readNBytes(1)[0]);
+ } else if (tagType == TagType.TAG_Short) {
+ nbt.setTag(tagName, BytesUtils.bytes2shortA(in.readNBytes(2)));
+ } else if (tagType == TagType.TAG_Int) {
+ nbt.setTag(tagName, BytesUtils.bytes2intA(in.readNBytes(4)));
+ } else if (tagType == TagType.TAG_Long) {
+ nbt.setTag(tagName, BytesUtils.bytes2longA(in.readNBytes(8)));
+ } else if (tagType == TagType.TAG_Float) {
+ nbt.setTag(tagName, BytesUtils.bytes2floatA(in.readNBytes(4)));
+ } else if (tagType == TagType.TAG_Double) {
+ nbt.setTag(tagName, BytesUtils.bytes2doubleA(in.readNBytes(8)));
+ } else if (tagType == TagType.TAG_Byte_Array) {
+ nbt.setTag(tagName, in.readNBytes(BytesUtils.bytes2intA(in.readNBytes(4))));
+ } else if (tagType == TagType.TAG_String) {
+ nbt.setTag(tagName, new String(in.readNBytes(BytesUtils.bytes2shortA(in.readNBytes(2)))));
+ } else if (tagType == TagType.TAG_List) {
+ nbt.setListTag(tagName, parseListTag(in));
+ } else if (tagType == TagType.TAG_Compound) {
+ nbt.setCompoundTag(tagName, parseCompoundTag(in));
+ } else if (tagType == TagType.TAG_Int_Array) {
+ int arraySize = BytesUtils.bytes2intA(in.readNBytes(4));//读取数组个数
+ int[] intdata = new int[arraySize];
+ for (int j = 0; j < arraySize; j++) {
+ intdata[j] = BytesUtils.bytes2intA(in.readNBytes(4));
+ }
+ nbt.setTag(tagName, intdata);
+ } else if (tagType == TagType.TAG_Long_Array) {
+ int arraySize = BytesUtils.bytes2intA(in.readNBytes(4));//读取数组个数
+ long[] intdata = new long[arraySize];
+ for (int j = 0; j < arraySize; j++) {
+ intdata[j] = BytesUtils.bytes2longA(in.readNBytes(8));
+ }
+ nbt.setTag(tagName, intdata);
+ } else if (tagType == TagType.TAG_End) {
+ return nbt;
+ }
+ }
+ }
+
+ /**
+ * 解析列表标签
+ *
+ * @param in 输入流
+ * @return 列表标签
+ * @throws IOException
+ */
+ private static ListTag parseListTag(InputStream in) throws IOException {
+
+ //读取列表类型
+ short ListTagType = BytesUtils.bytes2short(in.readNBytes(1));
+ //创建新列表
+ ListTag nbtList = new ListTag(ListTagType);
+ //获取列表成员数
+ int listSize = BytesUtils.bytes2intA(in.readNBytes(4));
+ //遍历所有成员
+ for (int i = 0; i < listSize; i++) {
+ if (ListTagType == TagType.TAG_Byte) {
+ nbtList.addTag(in.readNBytes(1)[0]);
+ } else if (ListTagType == TagType.TAG_Short) {
+ nbtList.addTag(BytesUtils.bytes2shortA(in.readNBytes(2)));
+ } else if (ListTagType == TagType.TAG_Int) {
+ nbtList.addTag(BytesUtils.bytes2intA(in.readNBytes(4)));
+ } else if (ListTagType == TagType.TAG_Long) {
+ nbtList.addTag(BytesUtils.bytes2longA(in.readNBytes(8)));
+ } else if (ListTagType == TagType.TAG_Float) {
+ nbtList.addTag(BytesUtils.bytes2floatA(in.readNBytes(4)));
+ } else if (ListTagType == TagType.TAG_Double) {
+ nbtList.addTag(BytesUtils.bytes2doubleA(in.readNBytes(8)));
+ } else if (ListTagType == TagType.TAG_Byte_Array) {
+ nbtList.addTag(in.readNBytes(BytesUtils.bytes2intA(in.readNBytes(4))));
+ } else if (ListTagType == TagType.TAG_String) {
+ nbtList.addTag(new String(in.readNBytes(BytesUtils.bytes2shortA(in.readNBytes(2)))));
+ } else if (ListTagType == TagType.TAG_List) {
+ nbtList.addTag(parseListTag(in));
+ } else if (ListTagType == TagType.TAG_Compound) {
+ nbtList.addTag(parseCompoundTag(in));
+ } else if (ListTagType == TagType.TAG_Int_Array) {
+ int arraySize = BytesUtils.bytes2intA(in.readNBytes(4));//读取数组个数
+ int[] intdata = new int[arraySize];
+ for (int j = 0; j < arraySize; j++) {
+ intdata[j] = BytesUtils.bytes2intA(in.readNBytes(4));
+ }
+ nbtList.addTag(intdata);
+ } else if (ListTagType == TagType.TAG_Long_Array) {
+ int arraySize = BytesUtils.bytes2intA(in.readNBytes(4));//读取数组个数
+ long[] longdata = new long[arraySize];
+ for (int j = 0; j < arraySize; j++) {
+ longdata[j] = BytesUtils.bytes2longA(in.readNBytes(8));
+ }
+ nbtList.addTag(longdata);
+ } else {
+ throw new NBTException("ListTag处理异常");
+ }
+ }
+
+ return nbtList;
+ }
+
+ /**
+ * 写标签
+ *
+ * @param nbt 标签
+ * @param out 输出流
+ * @throws IOException
+ */
+ public static void writeNBT(CompoundTag nbt, OutputStream out) throws IOException {
+ writeCompoundTag(nbt, out);
+ }
+
+ /**
+ * 写复合标签
+ *
+ * @param nbt 复合标签
+ * @param out 输出流
+ * @throws IOException
+ */
+ private static void writeCompoundTag(CompoundTag nbt, OutputStream out) throws IOException {
+
+
+ Iterator> integer = nbt.entrySet().iterator();
+ while (integer.hasNext()) {
+ Map.Entry tag = integer.next();
+ short tagType = TagType.Object2TagType(tag.getValue());
+ //判断标签类型
+ if (tagType == -1) {
+ throw new NBTException("错误的标签类型");
+ }
+
+ out.write(tagType);//写标签类型
+ out.write(BytesUtils.short2bytes((short) tag.getKey().getBytes().length));//标签名长度
+ out.write(tag.getKey().getBytes());
+
+
+ writeTag(tagType, tag.getValue(), out);
+
+ }
+
+ //out.write(TagType.TAG_End);//复合标签尾
+
+
+ }
+
+ /**
+ * 写出标签
+ *
+ * @param tagType 标签类型
+ * @param object 标签对象
+ * @param out 输出流
+ * @throws IOException
+ */
+ private static void writeTag(short tagType, Object object, OutputStream out) throws IOException {
+ if (tagType == TagType.TAG_Byte) {
+ out.write((byte) object);
+ } else if (tagType == TagType.TAG_Short) {
+ out.write(BytesUtils.short2bytes((short) object));
+ } else if (tagType == TagType.TAG_Int) {
+ out.write(BytesUtils.int2bytes((int) object));
+ } else if (tagType == TagType.TAG_Long) {
+ out.write(BytesUtils.long2bytes((long) object));
+ } else if (tagType == TagType.TAG_Float) {
+ out.write(BytesUtils.float2bytes((float) object));
+ } else if (tagType == TagType.TAG_Double) {
+ out.write(BytesUtils.double2bytes((double) object));
+ } else if (tagType == TagType.TAG_Byte_Array) {
+ out.write(BytesUtils.int2bytes(((byte[]) object).length));
+ out.write((byte[]) object);
+ } else if (tagType == TagType.TAG_String) {
+ out.write(BytesUtils.short2bytes((short) ((String) object).getBytes().length));
+ out.write(((String) object).getBytes());
+ } else if (tagType == TagType.TAG_List) {
+ writeListTag((ListTag) object, out);
+ } else if (tagType == TagType.TAG_Compound) {
+ writeCompoundTag((CompoundTag) object, out);
+ out.write(0);
+ } else if (tagType == TagType.TAG_Int_Array) {
+ out.write(BytesUtils.int2bytes(((int[]) object).length));
+ for (int ints : (int[]) object) {
+ out.write(BytesUtils.int2bytes(ints));
+ }
+ } else if (tagType == TagType.TAG_Long_Array) {
+ out.write(BytesUtils.int2bytes(((long[]) object).length));
+ for (long longs : (long[]) object) {
+ out.write(BytesUtils.long2bytes(longs));
+ }
+ }
+ }
+
+ /**
+ * 写出列表标签
+ *
+ * @param nbt 列表标签
+ * @param out 输出流
+ * @throws IOException
+ */
+ private static void writeListTag(ListTag nbt, OutputStream out) throws IOException {
+ short ListTagType = nbt.type;
+
+ /*out.write(TagType.TAG_List);//写标签类型
+ out.write(BytesUtils.short2bytes((short) nbt.Name.getBytes().length));//标签名长度
+ out.write(nbt.Name.getBytes());*/
+
+
+ out.write(ListTagType);//写列表类型
+ out.write(BytesUtils.int2bytes(nbt.size()));//写列表大小
+ for (int i = 0; i < nbt.size(); i++) {
+ Object object = nbt.get(i);
+ writeTag(ListTagType, object, out);
+
+ }
+
+
+ }
+
+ /**
+ * 读取Dat文件
+ *
+ * @param filePath 文件路径
+ * @return 读取的复合标签
+ * @throws IOException
+ */
+ public static CompoundTag readDATFile(File filePath) throws IOException {
+
+ FileInputStream fileInputStream = new FileInputStream(filePath);
+ CompoundTag tag = parseNBT(CompressUtils.gzipDecompress(fileInputStream.readAllBytes()));
+ fileInputStream.close();
+ return tag;
+
+ }
+
+ /**
+ * 写出Dat文件
+ *
+ * @param filePath 文件路径
+ * @param compoundTag 复合标签
+ * @throws IOException
+ */
+ public static void writeDATFile(File filePath, CompoundTag compoundTag) throws IOException {
+ FileOutputStream fileOutputStream = new FileOutputStream(filePath, false);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ writeNBT(compoundTag, byteArrayOutputStream);
+ fileOutputStream.write(CompressUtils.gzipCompress(byteArrayOutputStream.toByteArray()));
+ byteArrayOutputStream.close();
+ fileOutputStream.close();
+ }
+
+ /**
+ * 读取MCA文件 mca坐标
+ *
+ * @param path mca文件夹路径
+ * @param mcaPos mca文件位置
+ * @return mca对象
+ * @throws IOException
+ */
+ public static MCA readMCAFile(File path, MCPosInt mcaPos) throws IOException {
+ MCA mca = new MCA();
+ mca.Pos = mcaPos.clone();
+ File mcaFile = new File(path.getPath() + "\\r." + mcaPos.x + "." + mcaPos.z + ".mca");
+ if (!mcaFile.isFile()) {
+ mcaFile.createNewFile();
+ }
+
+ RandomAccessFile randomAccessFile = new RandomAccessFile(mcaFile, "r");
+ for (int i = 0; i < 1024; i++) {
+ //顺序读取区块索引表
+ randomAccessFile.seek(i * 4);
+ //读取区块偏移
+ int chunkPosOffset = BytesUtils.bytes2intA(BytesUtils.bytesSplicing(new byte[1], RandomAccessFileReadBytes(randomAccessFile, 3)));
+ if (chunkPosOffset == 0) {
+ //区块还未生成
+ continue;
+ }
+
+ //区块数据段大小
+ byte chunkSectionSize = randomAccessFile.readByte();
+
+
+ //跳转区块数据区
+ randomAccessFile.seek(chunkPosOffset * 4096);
+
+ int chunkSize = randomAccessFile.readInt();
+ byte compressType = randomAccessFile.readByte();
+ byte[] chunkData = new byte[chunkSize];
+ randomAccessFile.read(chunkData);
+ if (compressType == 1) {//GZip压缩
+ mca.chunksNBT[i] = MCUtil.parseNBT(CompressUtils.gzipDecompress(chunkData));
+ } else if (compressType == 2) {//ZLib压缩
+ try {
+ mca.chunksNBT[i] = (MCUtil.parseNBT(CompressUtils.zlibDecompress(chunkData)));
+ } catch (DataFormatException e) {
+ throw new NBTException("ZLIB解压失败");
+ }
+ } else if (compressType == 3) {//未压缩
+ mca.chunksNBT[i] = (MCUtil.parseNBT(chunkData));
+ } else {
+ System.out.println("未知压缩类型:" + String.valueOf(compressType));
+ //throw new NBTException("未知压缩类型:" + String.valueOf(compressType));
+ }
+ }
+ randomAccessFile.close();
+
+ return mca;
+ }
+
+ /**
+ * 写出MCA文件 全部修改
+ *
+ * @param mca mca对象
+ * @param path mca文件夹路径
+ * @throws IOException
+ */
+ public static void writeMCAFile(MCA mca, File path) throws IOException {
+
+ RandomAccessFile randomAccessFile = new RandomAccessFile(
+ new File(path.getPath() + "\\r." + mca.Pos.x + "." + mca.Pos.z + ".mca"), "rw");
+
+
+ randomAccessFile.setLength(0);//清除所有
+
+
+ long time = System.currentTimeMillis();//生成新时间戳
+
+ //数据区写区块
+ int chunksCount = 2;
+ for (int i = 0; i < 1024; i++) {
+ if (mca.chunksNBT[i] != null) {
+ //写区块数据
+ randomAccessFile.seek(chunksCount * 4096);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ MCUtil.writeNBT(mca.chunksNBT[i], byteArrayOutputStream);
+ byte[] NBTData = CompressUtils.zlibCompress(byteArrayOutputStream.toByteArray());
+ byteArrayOutputStream.close();
+ randomAccessFile.writeInt(NBTData.length);
+ randomAccessFile.write(2);
+ randomAccessFile.write(NBTData);
+
+
+ //写区块时间戳
+ randomAccessFile.seek(i * 4 + 4096);
+ randomAccessFile.writeInt((int) time);
+
+ //写区块位置
+ randomAccessFile.seek(i * 4);
+ randomAccessFile.write(BytesUtils.bytesCut(BytesUtils.int2bytes(chunksCount), 1, 0, 3));
+ randomAccessFile.write((int) Math.ceil(NBTData.length / 4096.0));
+
+ chunksCount += (int) Math.ceil(NBTData.length / 4096.0);
+
+ //mca.chunkLocation.put(chunksCount, (byte) Math.ceil(NBTData.length / 4096.0));
+ }
+ }
+
+
+ randomAccessFile.seek(8191 + chunksCount * 4096);
+ randomAccessFile.write(0);
+ randomAccessFile.close();
+
+ }
+
+ /**
+ * 写出MCA文件 局部修改
+ *
+ * @param mca mca对象
+ * @param path mca文件夹路径
+ * @throws IOException
+ */
+ public static void writeMCAFile2(MCA mca, File path) throws IOException {
+ RandomAccessFile randomAccessFile = new RandomAccessFile(
+ new File(path.getPath() + "\\r." + mca.Pos.x + "." + mca.Pos.z + ".mca"), "rw");
+ if (randomAccessFile.length() == 0)
+ randomAccessFile.setLength(8192);
+
+ long time = System.currentTimeMillis();//生成新时间戳
+
+ for (int i = 0; i < 1024; i++) {
+ if (mca.chunksFlag[i]) {//如果被标记 重写新数据
+ //读取区块信息表
+ randomAccessFile.seek(i * 4);
+ int chunkPosOffset;
+ chunkPosOffset = BytesUtils.bytes2intA(BytesUtils.bytesSplicing(new byte[1], RandomAccessFileReadBytes(randomAccessFile, 3)));
+ //区块数据段大小
+ byte chunkSectionSize = randomAccessFile.readByte();
+
+ /*if (chunkPosOffset == 0) {//未找到区块
+ //MCUtil.writeMCAFile(mca,path);
+ //throw new NBTException("保存时 未找到区块");
+ chunkSectionSize = -1;
+ }*/
+
+ //生成新区块数据
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ MCUtil.writeNBT(mca.chunksNBT[i], byteArrayOutputStream);
+ byte[] newChunkData = CompressUtils.zlibCompress(byteArrayOutputStream.toByteArray());
+ byteArrayOutputStream.close();
+
+ //计算新区块数据所占段数
+ byte newChunkSectionSize = (byte) Math.ceil(newChunkData.length / 4096.0);
+
+ //判断大小
+ if (chunkSectionSize < newChunkSectionSize) {
+ //System.out.println("遇到增容");
+ //比原先大 查找最大偏移
+ int maxChunkOffset = 0;
+ byte maxChunkSectionSize = 0;
+ int maxChunkIndex = 0;
+ for (int j = 0; j < 1024; j++) {//找到最后面位置
+ //读取区块信息表
+ randomAccessFile.seek(j * 4);
+ int maxChunkPosOffset = BytesUtils.bytes2intA(BytesUtils.bytesSplicing(new byte[1], RandomAccessFileReadBytes(randomAccessFile, 3)));
+ if (maxChunkPosOffset == 0) {
+ continue;
+ }
+ if (maxChunkOffset < maxChunkPosOffset) {
+ maxChunkOffset = maxChunkPosOffset;
+ maxChunkIndex = j;
+ //区块数据段大小
+ maxChunkSectionSize = randomAccessFile.readByte();
+ }
+
+ }
+ chunkPosOffset = maxChunkOffset + maxChunkSectionSize;//设置最后面的偏移
+ }
+
+ if (chunkPosOffset == 0) {
+ chunkPosOffset = 2;
+ }
+
+ //写区块数据
+ randomAccessFile.seek(chunkPosOffset * 4096);
+
+ randomAccessFile.writeInt(newChunkData.length);
+ randomAccessFile.write(2);
+ randomAccessFile.write(newChunkData);
+
+
+ //写区块时间戳
+ randomAccessFile.seek(i * 4 + 4096);
+ randomAccessFile.writeInt((int) time);
+
+ //写区块信息
+ randomAccessFile.seek(i * 4);
+ randomAccessFile.write(BytesUtils.bytesCut(BytesUtils.int2bytes(chunkPosOffset), 1, 0, 3));
+ randomAccessFile.write(newChunkSectionSize);
+ }
+ }
+
+ }
+
+ /**
+ * RandomAccessFile 读取N个字节
+ *
+ * @param randomAccessFile RandomAccessFile对象
+ * @param lenth 欲读取的字节长度
+ * @return 读取的字节数组
+ * @throws IOException
+ */
+ private static byte[] RandomAccessFileReadBytes(RandomAccessFile randomAccessFile, int lenth) throws IOException {
+ byte[] data = new byte[lenth];
+ randomAccessFile.read(data);
+ return data;
+ }
+}
diff --git a/NBTUtils/src/main/mc/MCA.java b/NBTUtils/src/main/mc/MCA.java
new file mode 100644
index 0000000..3129a95
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCA.java
@@ -0,0 +1,41 @@
+package main.mc;
+
+import main.exception.NBTException;
+import main.nbt.CompoundTag;
+import main.nbt.ListTag;
+import main.nbt.TagType;
+
+import java.util.HashMap;
+
+public class MCA {
+ public MCPosInt Pos;//当前MCA所在位置
+ public long[] chunkTimeStamp = new long[1024];//时间戳
+ public CompoundTag[] chunksNBT = new CompoundTag[1024];//区块数据
+ public boolean[] chunksFlag = new boolean[1024];//被读取或修改过的标记
+
+ /**
+ * 获取相对区块
+ *
+ * @param chunkIndex 区块索引
+ * @return 区块对象
+ */
+ public MCChunk getChunk(int chunkIndex) {
+ if (chunksNBT[chunkIndex] == null)
+ throw new NBTException("区块为空:" + chunkIndex);
+ chunksFlag[chunkIndex] = true;
+ return new MCChunk(chunksNBT[chunkIndex]);
+ }
+
+ /**
+ * 复制区块标签对象
+ *
+ * @param chunkIndex 区块索引
+ * @return 新的区块标签对象
+ */
+ public CompoundTag cloneChunkCompoundTag(int chunkIndex) {
+ if (chunksNBT[chunkIndex] == null)
+ throw new NBTException("区块为空:" + chunkIndex);
+ chunksFlag[chunkIndex] = true;
+ return chunksNBT[chunkIndex].clone();
+ }
+}
diff --git a/NBTUtils/src/main/mc/MCBlock.java b/NBTUtils/src/main/mc/MCBlock.java
new file mode 100644
index 0000000..11f3081
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCBlock.java
@@ -0,0 +1,69 @@
+package main.mc;
+
+import main.nbt.CompoundTag;
+
+public class MCBlock {
+ public CompoundTag blockState;//方块状态
+ public CompoundTag blockEntitie;//方块实体
+
+
+ public static final String FACING_UP = "up";
+ public static final String FACING_DOWN = "down";
+ public static final String FACING_NORTH = "north";
+ public static final String FACING_SOUTH = "south";
+ public static final String FACING_EAST = "east";
+ public static final String FACING_WEST = "west";
+
+ /**
+ * 通过标签创建方块
+ *
+ * @param blockState
+ * @param blockEntitie
+ */
+ public MCBlock(CompoundTag blockState, CompoundTag blockEntitie) {
+ this.blockState = blockState;
+ this.blockEntitie = blockEntitie;
+ }
+
+ /**
+ * 通过方块名创建方块
+ *
+ * @param blockName 方块名 如 "minecraft:air"
+ */
+ public MCBlock(String blockName) {
+ blockState = new CompoundTag();
+ blockState.setTag("Name", blockName);
+ }
+
+ /**
+ * 获取方块名
+ *
+ * @return 方块名
+ */
+ public String getBlockName() {
+ return (String) blockState.getTag("Name");
+ }
+
+
+ /**
+ * 设置方块朝向
+ *
+ * @param blockFacing 方块朝向 请使用"FACING_xx"
+ */
+ public void setBlockFacing(String blockFacing) {
+ blockState.setCompoundTag("Properties").setTag("facing", blockFacing);
+ }
+
+ /**
+ * 复制一个新对象
+ *
+ * @return 新方块
+ */
+ public MCBlock clone() {
+ if (blockEntitie == null)
+ return new MCBlock(blockState.clone(), null);
+ return new MCBlock(blockState.clone(), blockEntitie.clone());
+ }
+
+
+}
diff --git a/NBTUtils/src/main/mc/MCBlockColors.java b/NBTUtils/src/main/mc/MCBlockColors.java
new file mode 100644
index 0000000..cc6fbab
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCBlockColors.java
@@ -0,0 +1,64 @@
+package main.mc;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class MCBlockColors {
+ private ArrayList blocks = new ArrayList<>();
+
+ public MCBlockColors(File blockImgPath) throws IOException {
+ ArrayList FileList = new ArrayList<>();
+ File[] files = blockImgPath.listFiles();
+ for (File f : files) {
+ if (f.isFile()) {
+ FileList.add(f);
+ }
+ }
+
+ for (File file : FileList) {
+ BufferedImage img = ImageIO.read(file);
+ BufferedImage pix = new BufferedImage(1, 1, img.getType());
+ pix.createGraphics().drawImage(img, 0, 0, 1, 1, null);
+ blocks.add(new BlockColor(file.getName().substring(0, file.getName().length() - 4), new Color(pix.getRGB(0, 0))));
+ }
+
+ }
+
+
+ public static class BlockColor {
+ public String name;
+ public Color color;
+
+ public BlockColor(String name, Color color) {
+ this.name = name;
+ this.color = color;
+ }
+
+ public int compare(Color color2) {
+ return Math.abs(color2.getRed() - color.getRed()) + Math.abs(color2.getGreen() - color.getGreen()) + Math.abs(color2.getBlue() - color.getBlue());
+ }
+ }
+
+ public BlockColor colorFindBlock(Color color) {
+ if (color.getAlpha() == 0)
+ return new BlockColor("air", new Color(0, 0, 0, 0));
+ int minIndex = 0;
+ int min = 255 * 255 * 255;
+ for (int i = 0; i < blocks.size(); i++) {
+ int c = blocks.get(i).compare(color);
+ if (min > c) {
+ min = c;
+ minIndex = i;
+
+ }
+
+ }
+ return blocks.get(minIndex);
+ }
+
+
+}
diff --git a/NBTUtils/src/main/mc/MCBlocksCollective.java b/NBTUtils/src/main/mc/MCBlocksCollective.java
new file mode 100644
index 0000000..5de83d8
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCBlocksCollective.java
@@ -0,0 +1,158 @@
+package main.mc;
+
+import main.exception.NBTException;
+
+import java.util.ArrayList;
+
+public class MCBlocksCollective {
+ public MCPosInt lwh;//集合的宽高
+ public ArrayList bloks;//方块集合
+
+ public MCBlocksCollective(MCPosInt lwh) {
+ this.lwh = lwh;
+ bloks = new ArrayList(lwh.x * lwh.y * lwh.z);
+ for (int i = 0; i < lwh.x * lwh.y * lwh.z; i++) {
+ bloks.add(null);
+ }
+ }
+
+ /**
+ * 设置方块
+ *
+ * @param pos 要设置的坐标
+ * @param block 方块
+ */
+ public void setBlock(MCPosInt pos, MCBlock block) {
+ checkPos(pos);
+ bloks.set(pos.x + pos.z * lwh.x + pos.y * lwh.x * lwh.z, block);
+ }
+
+ /**
+ * 获取方块
+ *
+ * @param pos 要获取的坐标
+ * @return 方块
+ */
+ public MCBlock getBlock(MCPosInt pos) {
+ checkPos(pos);
+ return bloks.get(pos.x + pos.z * lwh.x + pos.y * lwh.x * lwh.z);
+ }
+
+ /**
+ * 检查坐标是否超集合
+ *
+ * @param pos 欲检查坐标
+ */
+ private void checkPos(MCPosInt pos) {
+ if ((pos.x < 0 || pos.x >= lwh.x) || (pos.y < 0 || pos.y >= lwh.y) || (pos.z < 0 || pos.z >= lwh.z))
+ throw new NBTException("超出范围:" + pos.toStr());
+ }
+
+ public MCBlocksCollective clone() {
+ MCBlocksCollective mcBlocksCollective = new MCBlocksCollective(lwh.clone());
+ mcBlocksCollective.bloks = (ArrayList) bloks.clone();
+ return mcBlocksCollective;
+ }
+
+ /**
+ * 空间翻转
+ *
+ * @param xFlip x翻转
+ * @param yFlip y翻转
+ * @param zFlip z翻转
+ * @return 自身
+ */
+ public MCBlocksCollective flip(boolean xFlip, boolean yFlip, boolean zFlip) {
+ MCBlocksCollective BlocksCollective = clone();
+ MCPosInt.iteratePos(lwh, p -> {
+ MCPosInt blockPos = p.clone();
+ if (xFlip)
+ blockPos.x = lwh.x - p.x - 1;
+ if (yFlip)
+ blockPos.y = lwh.y - p.y - 1;
+ if (zFlip)
+ blockPos.z = lwh.z - p.z - 1;
+ setBlock(p, BlocksCollective.getBlock(blockPos));
+ });
+ return this;
+ }
+
+ /**
+ * 空间旋转
+ *
+ * @param rotation 旋转的方向 0绕x轴 1绕y轴 2绕z轴
+ * @param times 旋转的次数 整数顺时针 负数逆时针
+ * @return 自身
+ */
+ public MCBlocksCollective rotation(int rotation, int times) {
+ while (times != 0) {
+ MCBlocksCollective BlocksCollective = clone();
+
+ {
+ int temp;
+ if (rotation == 0) {
+ temp = lwh.z;
+ lwh.z = lwh.y;
+ lwh.y = temp;
+ } else if (rotation == 1) {
+ temp = lwh.z;
+ lwh.z = lwh.x;
+ lwh.x = temp;
+ } else if (rotation == 2) {
+ temp = lwh.y;
+ lwh.y = lwh.x;
+ lwh.x = temp;
+ }
+ }
+
+ MCPosInt.iteratePos(lwh, p -> {
+ MCPosInt blockPos = p.clone();
+ int temp;
+ if (rotation == 0) {
+ temp = blockPos.z;
+ blockPos.z = blockPos.y;
+ blockPos.y = temp;
+ } else if (rotation == 1) {
+ temp = blockPos.z;
+ blockPos.z = blockPos.x;
+ blockPos.x = temp;
+ } else if (rotation == 2) {
+ temp = blockPos.y;
+ blockPos.y = blockPos.x;
+ blockPos.x = temp;
+ }
+
+
+ setBlock(p, BlocksCollective.getBlock(blockPos));
+ });
+
+ if (rotation == 0) {
+ flip(false, times > 0, times < 0);
+ } else if (rotation == 1) {
+ flip(times < 0, false, times > 0);
+ } else if (rotation == 2) {
+ flip(times > 0, times < 0, false);
+ }
+
+
+ if (times > 0)
+ times--;
+ else if (times < 0)
+ times++;
+
+ }
+ return this;
+ }
+
+ public MCBlocksCollective replace(replace r, MCBlock replaceBlock) {
+ MCPosInt.iteratePos(lwh, p -> {
+ if (r.isReplace(getBlock(p)))
+ setBlock(p, replaceBlock);
+ });
+ return this;
+ }
+
+ public interface replace {
+ boolean isReplace(MCBlock block);
+ }
+}
\ No newline at end of file
diff --git a/NBTUtils/src/main/mc/MCChunk.java b/NBTUtils/src/main/mc/MCChunk.java
new file mode 100644
index 0000000..b61d3ea
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCChunk.java
@@ -0,0 +1,203 @@
+package main.mc;
+
+import main.Utils.BytesUtils;
+import main.exception.NBTException;
+import main.io.MCUtil;
+import main.nbt.CompoundTag;
+import main.nbt.ListTag;
+import main.nbt.TagType;
+
+import java.util.BitSet;
+
+public class MCChunk {
+ public CompoundTag chunk;//区块对象
+
+ /**
+ * 通过区块标签创建区块对象
+ *
+ * @param chunk
+ */
+ public MCChunk(CompoundTag chunk) {
+ if (chunk == null)
+ throw new NBTException("区块为空");
+ this.chunk = chunk;
+ }
+
+ /**
+ * 复制区块对象
+ *
+ * @return 新的区块对象
+ */
+ public MCChunk cloneChunk() {
+ return new MCChunk(chunk.clone());
+ }
+
+ /**
+ * 获取区块坐标 Y可以忽略
+ *
+ * @return 区块坐标
+ */
+ public MCPosInt getChunkPos() {
+ return new MCPosInt((int) chunk.getTag("xPos"), (int) chunk.getTag("yPos"), (int) chunk.getTag("zPos"));
+ }
+
+ /**
+ * 在区块里寻找子区块
+ *
+ * @param Y 区块Y索引
+ * @return 子区块标签对象
+ */
+ public CompoundTag getSubChunk(int Y) {
+ ListTag subChunks = chunk.getCompoundTag("").getListTag("sections");
+ for (int i = 0; i < subChunks.size(); i++) {
+ CompoundTag subChunk = subChunks.getCompoundTag(i);
+ if ((byte) subChunk.getTag("Y") == (byte) Y) {
+ return subChunk;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * 获取方块实体对象
+ *
+ * @param pos 世界绝对坐标
+ * @return 方块实体标签
+ */
+ public CompoundTag getBlockEntitie(MCPosInt pos) {
+ ListTag chunkBlockEntities = chunk.getCompoundTag("").getListTag("block_entities");
+ if (chunkBlockEntities == null)
+ return null;
+ for (int i = 0; i < chunkBlockEntities.size(); i++) {
+ CompoundTag blockEntitie = chunkBlockEntities.getCompoundTag(i);
+ if ((int) blockEntitie.getTag("x") == pos.x)
+ if ((int) blockEntitie.getTag("y") == pos.y)
+ if ((int) blockEntitie.getTag("z") == pos.z)
+ return blockEntitie;
+ }
+ return null;
+ }
+
+ /**
+ * 设置方块实体对象
+ *
+ * @param pos 世界绝对坐标
+ * @param blockEntitie 方块实体标签
+ */
+ public void setBlockEntitie(MCPosInt pos, CompoundTag blockEntitie) {
+
+ ListTag chunkBlockEntities = chunk.getCompoundTag("").getListTag("block_entities");
+ if (chunkBlockEntities.type == TagType.TAG_End) {
+ chunkBlockEntities = new ListTag(TagType.TAG_Compound);
+ chunk.getCompoundTag("").setListTag("block_entities", chunkBlockEntities);
+ }
+
+ CompoundTag tag = blockEntitie.clone();
+
+ tag.setTag("x", pos.x);
+ tag.setTag("y", pos.y);
+ tag.setTag("z", pos.z);
+ for (int i = 0; i < chunkBlockEntities.size(); i++) {
+ if (chunkBlockEntities.getCompoundTag(i).getTag("x") != null)
+ if ((int) chunkBlockEntities.getCompoundTag(i).getTag("x") == pos.x)
+ if ((int) chunkBlockEntities.getCompoundTag(i).getTag("y") == pos.y)
+ if ((int) chunkBlockEntities.getCompoundTag(i).getTag("z") == pos.z) {
+ chunkBlockEntities.set(i, tag);
+ return;
+ }
+
+ }
+ chunkBlockEntities.addTag(tag);
+ return;
+ }
+
+ /**
+ * 获取一个方块状态
+ *
+ * @param blockIndex 方块索引
+ * @param subChunkY 子区块Y索引
+ * @return 方块状态标签
+ */
+ public CompoundTag getBlockState(int blockIndex, int subChunkY) {
+ CompoundTag sunChunkBlocks = getSubChunk(subChunkY).getCompoundTag("block_states");
+
+ //读取区块方块索引
+ ListTag blocks = sunChunkBlocks.getListTag("palette");
+
+
+ int mapBit = MCUtil.getMapBitSize(blocks.size());
+ int longIndex = blockIndex / (64 / mapBit);
+ int longBlockIndex = blockIndex % (64 / mapBit);
+ //System.out.println(sunChunkBlocks);
+
+ if (sunChunkBlocks.getTag("data") == null)
+ return blocks.getCompoundTag(0);
+ BitSet blockData = BitSet.valueOf(new long[]{((long[]) sunChunkBlocks.getTag("data"))[longIndex]});
+
+ //System.out.println(BytesUtils.bytes2int(blockData.get(longBlockIndex * mapBit, (longBlockIndex + 1) * mapBit).toByteArray()));
+
+ return blocks.getCompoundTag(BytesUtils.bytes2int(blockData.get(longBlockIndex * mapBit, (longBlockIndex + 1) * mapBit).toByteArray()));
+
+ }
+
+ /**
+ * 设置方块状态
+ *
+ * @param blockIndex 方块索引
+ * @param subChunkY 子区块Y索引
+ * @param blockState 方块状态标签
+ */
+ public void setBlockState(int blockIndex, int subChunkY, CompoundTag blockState) {
+ CompoundTag subChunk = getSubChunk(subChunkY);
+ if (subChunk == null) {
+ throw new NBTException("Y越界:" + subChunkY);
+ }
+ CompoundTag sunChunkBlocks = subChunk.getCompoundTag("block_states");
+ //读取区块方块索引
+ ListTag blocks = sunChunkBlocks.getListTag("palette");
+ int mapBitBefore = MCUtil.getMapBitSize(blocks.size());
+
+ //防止为空
+ if (sunChunkBlocks.getTag("data") == null)
+ sunChunkBlocks.setTag("data", new long[1]);
+
+
+ int foundBlockIndex = -1;
+ //查找方块列表是否存在要添加的方块
+ for (int i = 0; i < blocks.size(); i++) {
+ if (blocks.getCompoundTag(i).equals(blockState)) {
+ foundBlockIndex = i;
+ break;
+ }
+ }
+
+ //未找到 添加方块列表
+ if (foundBlockIndex == -1) {
+ blocks.addTag(blockState);
+ foundBlockIndex = blocks.size() - 1;
+ }
+
+ int mapBitNow = MCUtil.getMapBitSize(blocks.size());
+ MCSubChunk mcSubChunkBefore = new MCSubChunk((long[]) sunChunkBlocks.getTag("data"), (int) Math.ceil(4096.0 / (double) (64 / mapBitBefore)), mapBitBefore);
+ if (mapBitNow != mapBitBefore) {//存储位数不一样
+ MCSubChunk mcSubChunkNow = new MCSubChunk((int) Math.ceil(4096.0 / (double) (64 / mapBitNow)), mapBitNow);
+ for (int i = 0; i < 4096; i++) {
+ if (blockIndex == i) {//找到欲替换方块
+ mcSubChunkNow.set(i, foundBlockIndex);
+ } else {//转移数据
+ mcSubChunkNow.set(i, mcSubChunkBefore.get(i));
+ }
+
+ }
+ sunChunkBlocks.setTag("data", mcSubChunkNow.getLongArray());
+ } else {
+ mcSubChunkBefore.set(blockIndex, foundBlockIndex);
+ sunChunkBlocks.setTag("data", mcSubChunkBefore.getLongArray());
+ }
+
+
+ }
+
+
+}
diff --git a/NBTUtils/src/main/mc/MCMap.java b/NBTUtils/src/main/mc/MCMap.java
new file mode 100644
index 0000000..22a70b2
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCMap.java
@@ -0,0 +1,145 @@
+package main.mc;
+
+import main.exception.NBTException;
+import main.io.MCUtil;
+import main.nbt.CompoundTag;
+import text.lib.MyImageFilter;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class MCMap {
+ private CompoundTag map;
+ private File mapFile;
+
+ /**
+ * 创建地图对象
+ *
+ * @param mapFile 地图文件
+ * @throws IOException
+ */
+ public MCMap(File mapFile) throws IOException {
+ this.mapFile = mapFile;
+ map = MCUtil.readDATFile(mapFile);
+ }
+
+ /**
+ * 保存地图文件
+ *
+ * @throws IOException
+ */
+ public void saveFile() throws IOException {
+ MCUtil.writeDATFile(mapFile, map);
+ }
+
+ /**
+ * 设置地图被制图台锁定
+ *
+ * @param locked 是否锁定
+ */
+ public void setLocked(boolean locked) {
+ map.getCompoundTag("").getCompoundTag("data").setTag("locked", (byte) (locked ? 1 : 0));
+ }
+
+ /**
+ * 地图在游戏世界里的位置中心
+ *
+ * @param xz 位置中心坐标
+ */
+ public void setXZCenter(MCPosInt xz) {
+ map.getCompoundTag("").getCompoundTag("data").setTag("xCenter", xz.x);
+ map.getCompoundTag("").getCompoundTag("data").setTag("zCenter", xz.z);
+ }
+
+ /**
+ * 设置位置箭头是否被显示
+ *
+ * @param trackingPosition 是否被显示
+ */
+ public void setTrackingPosition(boolean trackingPosition) {
+ map.getCompoundTag("").getCompoundTag("data").setTag("trackingPosition", (byte) (trackingPosition ? 1 : 0));
+ }
+
+ /**
+ * 设置玩家位置指示器是否显示
+ *
+ * @param unlimitedTracking 是否被显示
+ */
+ public void setUnlimitedTracking(boolean unlimitedTracking) {
+ map.getCompoundTag("").getCompoundTag("data").setTag("unlimitedTracking", (byte) (unlimitedTracking ? 1 : 0));
+ }
+
+ /**
+ * 设置地图缩放等级
+ *
+ * @param scale 缩放等级(最大为4)
+ */
+ public void setScale(int scale) {
+ if (scale < 0)
+ scale = 0;
+ if (scale > 4)
+ scale = 4;
+ map.getCompoundTag("").getCompoundTag("data").setTag("scale", (byte) scale);
+ }
+
+
+ /**
+ * 获取某像素颜色
+ *
+ * @param x 水平像素位置
+ * @param y 垂直像素位置
+ * @return 返回获取的颜色
+ */
+ public Color getColor(int x, int y) {
+ return MCMapColors.byte2color(((byte[]) (map.getCompoundTag("").getCompoundTag("data").getTag("colors")))[x + y * 128]);
+ }
+
+ /**
+ * 设置某像素颜色
+ *
+ * @param x 水平像素位置
+ * @param y 垂直像素位置
+ * @param color 要设置的颜色
+ */
+ public void setColor(int x, int y, Color color) {
+ ((byte[]) (map.getCompoundTag("").getCompoundTag("data").getTag("colors")))[x + y * 128] = MCMapColors.color2byte(color);
+ }
+
+ /**
+ * 设置图片 (128x128)
+ *
+ * @param inputStream 图片输入流
+ * @throws IOException
+ */
+ public void setImg(InputStream inputStream) throws IOException {
+ BufferedImage bufferedImage = ImageIO.read(inputStream);
+ for (int y = 0; y < 128; y++) {
+ for (int x = 0; x < 128; x++) {
+ setColor(x, y, new Color(bufferedImage.getRGB(x, y)));
+ }
+ }
+
+ }
+
+ /**
+ * 输出png图片 (128x128)
+ *
+ * @param outputStream
+ * @throws IOException
+ */
+ public void save2img(OutputStream outputStream) throws IOException {
+ BufferedImage bufferedImage = new BufferedImage(128, 128, BufferedImage.TYPE_4BYTE_ABGR);
+ for (int y = 0; y < 128; y++) {
+ for (int x = 0; x < 128; x++) {
+ bufferedImage.setRGB(x, y, getColor(x, y).getRGB());
+ }
+ }
+ ImageIO.write(bufferedImage, "png", outputStream);
+ }
+
+}
diff --git a/NBTUtils/src/main/mc/MCMapColors.java b/NBTUtils/src/main/mc/MCMapColors.java
new file mode 100644
index 0000000..60f2f8d
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCMapColors.java
@@ -0,0 +1,154 @@
+package main.mc;
+
+import main.Utils.BytesUtils;
+
+import java.awt.*;
+
+/*
+ * 资料:
+ * https://minecraft.fandom.com/zh/wiki/%E5%9C%B0%E5%9B%BE%E7%89%A9%E5%93%81%E6%A0%BC%E5%BC%8F
+ * */
+public class MCMapColors {
+ //MC地图颜色索引
+ public static final Color[] mapColors = {
+ new Color(0, 0, 0, 0),
+ new Color(127, 178, 56),
+ new Color(247, 233, 163),
+ new Color(199, 199, 199),
+ new Color(255, 0, 0),
+ new Color(160, 160, 255),
+ new Color(167, 167, 167),
+ new Color(0, 124, 0),
+ new Color(255, 255, 255),
+ new Color(164, 168, 184),
+ new Color(151, 109, 77),
+ new Color(112, 112, 112),
+ new Color(64, 64, 255),
+ new Color(143, 119, 72),
+ new Color(255, 252, 245),
+ new Color(216, 127, 51),
+ new Color(178, 76, 216),
+ new Color(102, 153, 216),
+ new Color(229, 229, 51),
+ new Color(127, 204, 25),
+ new Color(242, 127, 165),
+ new Color(76, 76, 76),
+ new Color(153, 153, 153),
+ new Color(76, 127, 153),
+ new Color(127, 63, 178),
+ new Color(51, 76, 178),
+ new Color(102, 76, 51),
+ new Color(102, 127, 51),
+ new Color(153, 51, 51),
+ new Color(25, 25, 25),
+ new Color(250, 238, 77),
+ new Color(92, 219, 213),
+ new Color(74, 128, 255),
+ new Color(0, 217, 58),
+ new Color(129, 86, 49),
+ new Color(112, 2, 0),
+ new Color(209, 177, 161),
+ new Color(159, 82, 36),
+ new Color(149, 87, 108),
+ new Color(112, 108, 138),
+ new Color(186, 133, 36),
+ new Color(103, 117, 53),
+ new Color(160, 77, 78),
+ new Color(57, 41, 35),
+ new Color(135, 107, 98),
+ new Color(87, 92, 92),
+ new Color(122, 73, 88),
+ new Color(76, 62, 92),
+ new Color(76, 50, 35),
+ new Color(76, 82, 42),
+ new Color(142, 60, 46),
+ new Color(37, 22, 16),
+ new Color(189, 48, 49),
+ new Color(148, 63, 97),
+ new Color(92, 25, 29),
+ new Color(22, 126, 134),
+ new Color(58, 142, 140),
+ new Color(86, 44, 62),
+ new Color(20, 180, 133),
+ new Color(86, 86, 86),
+ new Color(186, 150, 126)
+ };
+
+ //相关联的地图色 乘数
+ public static final float[] relatedColor = {
+ 0.71f,
+ 0.86f,
+ 1f,
+ 0.53f
+ };
+
+ /**
+ * 计算颜色差
+ *
+ * @param color 比对颜色1
+ * @param color2 比对颜色2
+ * @return 颜色差
+ */
+ public static int compareColor(Color color, Color color2) {
+ return Math.abs(color2.getRed() - color.getRed()) + Math.abs(color2.getGreen() - color.getGreen()) + Math.abs(color2.getBlue() - color.getBlue());
+ }
+
+/* HSB计算色差 (效果不咋滴 废弃)
+ public static int compareColor(Color color, Color color2) {
+ float[] HSB = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
+ float[] HSB2 = Color.RGBtoHSB(color2.getRed(), color2.getGreen(), color2.getBlue(), null);
+
+ HSB[0] = (Math.abs(HSB[0] - HSB2[0])) * 1000f;
+ HSB[1] = (Math.abs(HSB[1] - HSB2[1])) * 100f;
+ HSB[2] = (Math.abs(HSB[2] - HSB2[2])) * 100f;
+
+ return (int) (HSB[0] + HSB[1] + HSB[2]);
+ }
+*/
+
+ /**
+ * 颜色乘
+ *
+ * @param color 欲被乘的颜色
+ * @param n 被乘的小数
+ * @return 乘后的颜色
+ */
+ public static Color multiplyColor(Color color, float n) {
+ return new Color((int) (color.getRed() * n), (int) (color.getGreen() * n), (int) (color.getBlue() * n));
+ }
+
+ /**
+ * 颜色到字节
+ *
+ * @param color 颜色
+ * @return 颜色字节
+ */
+ public static byte color2byte(Color color) {
+ byte iMin = 0;
+ byte jMin = 0;
+ int colorDeviation = 16581375;
+ for (byte i = 0; i < 61; i++) {//所有色
+ for (byte j = 0; j < 4; j++) {//关联色
+ int Deviation = compareColor(color, multiplyColor(mapColors[i], relatedColor[j]));
+ if (Deviation < colorDeviation) {
+ colorDeviation = Deviation;
+ iMin = i;
+ jMin = j;
+ }
+ }
+ }
+ return (byte) ((iMin * 4 + jMin));
+ }
+
+ /**
+ * 字节到颜色
+ *
+ * @param color 颜色字节
+ * @return 返回颜色
+ */
+ public static Color byte2color(byte color) {
+ return multiplyColor(mapColors[(color & 0xFF) / 4], relatedColor[(color & 0xFF) % 4]);
+ }
+
+
+}
diff --git a/NBTUtils/src/main/mc/MCPosInt.java b/NBTUtils/src/main/mc/MCPosInt.java
new file mode 100644
index 0000000..324bcf4
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCPosInt.java
@@ -0,0 +1,222 @@
+package main.mc;
+
+public class MCPosInt {
+ public int x, y, z;
+
+ public MCPosInt() {
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+ }
+
+ /**
+ * 通过xyz创建坐标对象
+ *
+ * @param x
+ * @param y
+ * @param z
+ */
+ public MCPosInt(int x, int y, int z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ /**
+ * 通过xz创建坐标对象
+ *
+ * @param x
+ * @param z
+ */
+ public MCPosInt(int x, int z) {
+ this.x = x;
+ this.y = 0;
+ this.z = z;
+ }
+
+ /**
+ * 坐标到字符串
+ *
+ * @return 格式化后的坐标文本
+ */
+ public String toStr() {
+ return "x:" + x + " y:" + y + " z:" + z;
+ }
+
+ /**
+ * 世界坐标取区块位置
+ *
+ * @param xyz 世界绝对坐标
+ * @return 区块绝对坐标
+ */
+ public static MCPosInt pos2chunk(MCPosInt xyz) {
+ return new MCPosInt(xyz.x >> 4, xyz.y >> 4, xyz.z >> 4);
+ }
+
+ /**
+ * 世界坐标取方块索引
+ *
+ * @param xyz 世界绝对坐标
+ * @return 方块索引值
+ */
+ public static int pos2blockIndex(MCPosInt xyz) {
+ return (xyz.x & 0xF) + (xyz.y & 0xF) * 256 + (xyz.z & 0xF) * 16;
+ }
+
+ /**
+ * 世界坐标Y到子区块Y索引
+ *
+ * @param y 世界绝对y坐标
+ * @return 子区块Y索引
+ */
+ public static int pos2subChunkY(int y) {
+ return y >> 4;
+ }
+
+ /**
+ * 区块坐标取区块索引
+ *
+ * @param xz 区块绝对坐标
+ * @return 区块索引
+ */
+ public static int chunk2chunkIndex(MCPosInt xz) {
+ return (xz.x & 0x1F) + (xz.z & 0x1F) * 32;
+ }
+
+
+ /**
+ * 世界坐标取区块索引
+ *
+ * @param xz 世界绝对坐标
+ * @return 区块索引
+ */
+ public static int pos2chunkIndex(MCPosInt xz) {
+ MCPosInt rel = pos2chunk(xz);
+ return chunk2chunkIndex(rel);
+ }
+
+
+ /**
+ * 将世界坐标转成mca位置
+ *
+ * @param xz 世界绝对坐标
+ * @return mca文件位置
+ */
+ public static MCPosInt pos2regionPos(MCPosInt xz) {
+ return new MCPosInt(xz.x >> 9, xz.z >> 9);
+ }
+
+ /**
+ * 将区块位置转成mca位置
+ *
+ * @param xz 区块位置
+ * @return mca文件位置
+ */
+ public static MCPosInt chunk2regionPos(MCPosInt xz) {
+ return new MCPosInt(xz.x >> 5, xz.z >> 5);
+ }
+
+ /**
+ * 复制个全新的坐标对象
+ *
+ * @return 新的坐标对象
+ */
+ public MCPosInt clone() {
+ return new MCPosInt(x, y, z);
+ }
+
+ /**
+ * 两坐标取原点坐标 和 区域大小
+ *
+ * @param p1 坐标1
+ * @param p2 坐标2
+ * @param Origin 原点坐标
+ * @param LWH 长宽高
+ */
+ public static void getOrigin(MCPosInt p1, MCPosInt p2, MCPosInt Origin, MCPosInt LWH) {
+ LWH.x = Math.abs(p1.x - p2.x);
+ LWH.y = Math.abs(p1.y - p2.y);
+ LWH.z = Math.abs(p1.z - p2.z);
+ Origin.x = Math.min(p1.x, p2.x);
+ Origin.y = Math.min(p1.y, p2.y);
+ Origin.z = Math.min(p1.z, p2.z);
+ }
+
+ /**
+ * 将两坐标相加
+ *
+ * @param p1 坐标1
+ * @param p2 坐标2
+ * @return 相加后的坐标
+ */
+ public static MCPosInt additive(MCPosInt p1, MCPosInt p2) {
+ return new MCPosInt(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z);
+ }
+
+
+ /**
+ * 和坐标比较
+ *
+ * @param xyz 要比较的坐标
+ * @return 是否一样
+ */
+ public boolean isEquals(MCPosInt xyz) {
+ return (xyz.x == x && xyz.y == y && xyz.z == z);
+ }
+
+ public static void iteratePos(MCPosInt lwh, callBack cb) {
+ for (int y = 0; y < lwh.y; y++) {
+ for (int z = 0; z < lwh.z; z++) {
+ for (int x = 0; x < lwh.x; x++) {
+ cb.iterate(new MCPosInt(x, y, z));
+ }
+ }
+ }
+
+ }
+
+
+ public static float getDistance(MCPosInt p1, MCPosInt p2) {
+ return (float) Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2) + Math.pow(p2.z - p1.z, 2));
+ }
+
+
+ /**
+ * 枚举两坐标直线所有坐标
+ *
+ * @param p1 坐标1
+ * @param p2 坐标2
+ * @param s 细分
+ * @param p 所有坐标
+ */
+ public static void enumLinePos(MCPosInt p1, MCPosInt p2, float s, callBack p) {
+ enumLinePos(p1, p2, s, (p_, d) -> {
+ p.iterate(p_);
+ });
+ }
+
+
+ public static void enumLinePos(MCPosInt p1, MCPosInt p2, float s, callBack2 p) {
+ float k = (MCPosInt.getDistance(p1, p2)) * s;
+ for (float i = 0; i < k; i++) {
+ MCPosInt pos = new MCPosInt(
+ Math.round((p1.x - (p1.x - p2.x) / k * i)),
+ Math.round((p1.y - (p1.y - p2.y) / k * i)),
+ Math.round((p1.z - (p1.z - p2.z) / k * i)));
+ p.iterate(pos, 1f / k * i);
+ }
+ if (k == 0) {
+ p.iterate(p1, 1);
+ }
+ }
+
+
+ public interface callBack {
+ void iterate(MCPosInt p);
+ }
+
+ public interface callBack2 {
+ void iterate(MCPosInt p, float d);
+ }
+
+}
diff --git a/NBTUtils/src/main/mc/MCRegion.java b/NBTUtils/src/main/mc/MCRegion.java
new file mode 100644
index 0000000..8f6a195
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCRegion.java
@@ -0,0 +1,309 @@
+package main.mc;
+
+import main.exception.NBTException;
+import main.io.MCUtil;
+import main.nbt.CompoundTag;
+import main.nbt.ListTag;
+
+import java.io.File;
+import java.io.IOException;
+
+
+public class MCRegion {
+ public static final short SAVEMODE_RewriteAll = 0;//0全部重写 稳定 需要退出存档
+ public static final short SAVEMODE_RewritePart = 1;//1局部重写 速度快 只需区块不被加载就能写
+ private int saveMode = SAVEMODE_RewritePart;//保存模式 默认为局部重写
+ private File mcaFileDirectory;//MCA文件目录的路径
+ private MCA[] mca;//当前打开的MCA
+ private int mcaCache = 3;//用于缓存mca文件 加快反复横跳速度
+ private int mcaCacheNumber = 0;//已存在的缓存数量
+ private MCChunk generateChunk = null;//自动生成区块 适合超平坦使用
+
+ /**
+ * @param mcaFileDirectory mca文件夹路径
+ * @throws IOException
+ */
+ public MCRegion(File mcaFileDirectory) throws IOException {
+ if (!mcaFileDirectory.isDirectory()) {
+ throw new NBTException("需要一个文件夹路径");
+ }
+ this.mcaFileDirectory = mcaFileDirectory;
+ }
+
+ /**
+ * @param mcaFileDirectory mca文件夹路径
+ * @param mcaCache 缓存数量 默认为3
+ * @throws IOException
+ */
+ public MCRegion(File mcaFileDirectory, int mcaCache) throws IOException {
+ if (!mcaFileDirectory.isDirectory()) {
+ throw new NBTException("需要一个文件夹路径");
+ }
+ this.mcaFileDirectory = mcaFileDirectory;
+ this.mcaCache = mcaCache;
+ }
+
+ /**
+ * 设置保存模式
+ *
+ * @param saveMode 保存模式 默认为局部重写 请使用"SAVEMODE_xx"
+ */
+ public void setSaveMode(int saveMode) {
+ this.saveMode = saveMode;
+ }
+
+ /**
+ * 设置生成区块 遇到空区块时会自动创建区块 为空时开摆
+ *
+ * @param generateChunk 要自动生成的区块
+ */
+ public void setGenerateChunk(MCChunk generateChunk) {
+ this.generateChunk = generateChunk;
+ }
+
+
+ /**
+ * 保存所有MCA文件
+ *
+ * @throws IOException
+ */
+ public void saveMCA() throws IOException {
+ for (int i = 0; i < mcaCacheNumber; i++) {
+ saveMCA(i);
+ }
+ }
+
+ /**
+ * 保存MCA文件
+ *
+ * @param index 缓存索引
+ * @throws IOException
+ */
+ private void saveMCA(int index) throws IOException {
+ System.out.println("保存mca:" + mca[index].Pos.toStr());
+ if (saveMode == SAVEMODE_RewriteAll) {
+ MCUtil.writeMCAFile(mca[index], mcaFileDirectory);
+ } else if (saveMode == SAVEMODE_RewritePart) {
+ MCUtil.writeMCAFile2(mca[index], mcaFileDirectory);
+ }
+ }
+
+ /**
+ * 区块坐标查mca缓存 如果没有则读取mca
+ *
+ * @param xz 区块绝对坐标
+ * @return 缓存索引
+ * @throws IOException
+ */
+ private int checkMCA(MCPosInt xz) throws IOException {
+ MCPosInt mcaPos = MCPosInt.chunk2regionPos(xz);//转换坐标
+ int chunkIndex = MCPosInt.chunk2chunkIndex(xz);
+ if (mca == null) {//初始化
+ mca = new MCA[mcaCache];
+ mca[0] = MCUtil.readMCAFile(mcaFileDirectory, mcaPos);
+ mcaCacheNumber = 1;
+ System.out.println("载入mca:" + mcaPos.toStr());
+ mca[0].chunkTimeStamp[chunkIndex] = System.currentTimeMillis();//修改时间戳
+ mca[0].chunksFlag[chunkIndex] = true;
+ if (generateChunk != null)//自动生成区块
+ if (mca[0].chunksNBT[chunkIndex] == null) {
+ mca[0].chunksNBT[chunkIndex] = generateChunk.cloneChunk().chunk;
+ }
+ return 0;
+ }
+
+ //查找
+ for (int i = 0; i < mcaCacheNumber; i++) {
+ if (mca[i].Pos.isEquals(mcaPos)) {
+ mca[i].chunkTimeStamp[chunkIndex] = System.currentTimeMillis();//修改时间戳
+ mca[i].chunksFlag[chunkIndex] = true;
+ if (generateChunk != null)//自动生成区块
+ if (mca[i].chunksNBT[chunkIndex] == null)
+ mca[i].chunksNBT[chunkIndex] = generateChunk.cloneChunk().chunk;
+ return i;
+ }
+ }
+
+ //保存末尾
+ if (mcaCacheNumber < mcaCache) {
+ mcaCacheNumber++;
+ } else {
+ saveMCA(mcaCache - 1);
+ }
+
+ //移动
+ System.arraycopy(mca, 0, mca, 1, mcaCache - 1);
+
+ //载入
+ mca[0] = MCUtil.readMCAFile(mcaFileDirectory, mcaPos);
+ System.out.println("载入mca:" + mcaPos.toStr());
+
+ mca[0].chunkTimeStamp[chunkIndex] = System.currentTimeMillis();//修改时间戳
+ mca[0].chunksFlag[chunkIndex] = true;
+ if (generateChunk != null)//自动生成区块
+ if (mca[0].chunksNBT[chunkIndex] == null)
+ mca[0].chunksNBT[chunkIndex] = generateChunk.cloneChunk().chunk;
+ return 0;
+ }
+
+
+ /**
+ * 获取方块状态
+ *
+ * @param xyz 方块绝对坐标
+ * @return 新的Tag对象
+ * @throws IOException
+ */
+ public CompoundTag getBlockState(MCPosInt xyz) throws IOException {
+ int index = checkMCA(MCPosInt.pos2chunk(xyz));
+ MCChunk chunk = mca[index].getChunk(MCPosInt.pos2chunkIndex(xyz));
+ return chunk.getBlockState(MCPosInt.pos2blockIndex(xyz), MCPosInt.pos2subChunkY(xyz.y)).clone();
+ }
+
+ /**
+ * 设置方块状态
+ *
+ * @param xyz 方块绝对坐标
+ * @param blockState 方块状态
+ * @throws IOException
+ */
+ public void setBlockState(MCPosInt xyz, CompoundTag blockState) throws IOException {
+ int index = checkMCA(MCPosInt.pos2chunk(xyz));
+ MCChunk chunk = mca[index].getChunk(MCPosInt.pos2chunkIndex(xyz));
+ chunk.setBlockState(MCPosInt.pos2blockIndex(xyz), MCPosInt.pos2subChunkY(xyz.y), blockState);
+ }
+
+ /**
+ * 获取方块实体
+ *
+ * @param xyz 方块绝对坐标
+ * @return 方块实体标签
+ * @throws IOException
+ */
+ public CompoundTag getBlockEntitie(MCPosInt xyz) throws IOException {
+ int index = checkMCA(MCPosInt.pos2chunk(xyz));
+ MCChunk chunk = mca[index].getChunk(MCPosInt.pos2chunkIndex(xyz));
+ return chunk.getBlockEntitie(xyz);
+ }
+
+ /**
+ * 设置方块实体 直接覆盖
+ *
+ * @param xyz 方块绝对坐标
+ * @param blockEntitie 方块实体标签
+ * @throws IOException
+ */
+ public void setBlockEntitie(MCPosInt xyz, CompoundTag blockEntitie) throws IOException {
+ int index = checkMCA(MCPosInt.pos2chunk(xyz));
+ MCChunk chunk = mca[index].getChunk(MCPosInt.pos2chunkIndex(xyz));
+ chunk.setBlockEntitie((xyz), blockEntitie);
+ }
+
+ /**
+ * 获取一个区块
+ *
+ * @param xz 区块绝对坐标
+ * @return 区块对象
+ * @throws IOException
+ */
+ public MCChunk getChunk(MCPosInt xz) throws IOException {
+ int index = checkMCA(xz);
+ return mca[index].getChunk(MCPosInt.chunk2chunkIndex(xz)).cloneChunk();
+ }
+
+ /**
+ * 设置区块
+ *
+ * @param xz 区块绝对坐标
+ * @param chunk 要设置的区块对象
+ * @throws IOException
+ */
+ public void setChunk(MCPosInt xz, MCChunk chunk) throws IOException {
+ int index = checkMCA(xz);
+ chunk.chunk.getCompoundTag("").setTag("xPos", xz.x).setTag("zPos", xz.z);
+ ListTag blockEntities = chunk.chunk.getCompoundTag("").getListTag("block_entities");
+ if (blockEntities != null) {
+ for (int i = 0; i < blockEntities.size(); i++) {
+ CompoundTag tag = blockEntities.getCompoundTag(i);
+ MCPosInt blockPos = new MCPosInt((int) tag.getTag("x"), (int) tag.getTag("z"));
+ blockPos.x = blockPos.x % 16 + xz.x * 16;
+ blockPos.z = blockPos.z % 16 + xz.z * 16;
+ tag.setTag("x", blockPos.x);
+ tag.setTag("z", blockPos.z);
+ }
+ }
+ mca[index].chunksNBT[MCPosInt.chunk2chunkIndex(xz)] = chunk.chunk;
+ }
+
+ /**
+ * 获取方块
+ *
+ * @param xyz 要获取的坐标
+ * @return 获取到的方块
+ * @throws IOException
+ */
+ public MCBlock getBlock(MCPosInt xyz) throws IOException {
+ return new MCBlock(getBlockState(xyz), getBlockEntitie(xyz));
+ }
+
+ /**
+ * 设置方块
+ *
+ * @param xyz 要设置的坐标
+ * @param block 要设置的方块
+ * @throws IOException
+ */
+ public void setBlock(MCPosInt xyz, MCBlock block) throws IOException {
+ setBlockState(xyz, block.blockState);
+ if (block.blockEntitie != null)
+ setBlockEntitie(xyz, block.blockEntitie);
+ }
+
+ /**
+ * 获取方块集
+ *
+ * @param Pos1 坐标1
+ * @param Pos2 坐标2
+ * @return 方块集
+ * @throws IOException
+ */
+ public MCBlocksCollective getBlocksCollective(MCPosInt Pos1, MCPosInt Pos2) throws IOException {
+ MCPosInt origin = new MCPosInt();
+ MCPosInt lwh = new MCPosInt();
+ MCPosInt.getOrigin(Pos1, Pos2, origin, lwh);
+ lwh = MCPosInt.additive(lwh, new MCPosInt(1, 1, 1));
+ MCBlocksCollective mcBlocksCollective = new MCBlocksCollective(lwh);
+
+ MCPosInt.iteratePos(lwh, p -> {
+ MCPosInt blockPos = MCPosInt.additive(origin, p);
+ MCBlock mcBlock = null;
+ try {
+ mcBlock = getBlock(blockPos);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ mcBlocksCollective.setBlock(p, mcBlock);
+ });
+
+
+ return mcBlocksCollective;
+ }
+
+ /**
+ * 设置方块集
+ *
+ * @param pos 原点坐标
+ * @param mcBlocksCollective 方块集
+ */
+ public void setBlocksCollective(MCPosInt pos, MCBlocksCollective mcBlocksCollective) {
+ MCPosInt.iteratePos(mcBlocksCollective.lwh, p -> {
+ MCBlock block = mcBlocksCollective.getBlock(p);
+ if (block != null)
+ try {
+ setBlock(MCPosInt.additive(pos, p), block.clone());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+}
diff --git a/NBTUtils/src/main/mc/MCSubChunk.java b/NBTUtils/src/main/mc/MCSubChunk.java
new file mode 100644
index 0000000..9e16b05
--- /dev/null
+++ b/NBTUtils/src/main/mc/MCSubChunk.java
@@ -0,0 +1,78 @@
+package main.mc;
+
+import main.exception.NBTException;
+
+public class MCSubChunk {
+ private long[] longArray;//地图数据
+ private int mapBit;//存储的位大小
+ private long max;
+
+ public MCSubChunk(long[] longArray, int longLength, int mapBit) {
+ this.longArray = new long[longLength];
+ System.arraycopy(longArray, 0, this.longArray, 0, longArray.length);
+ this.mapBit = mapBit;
+ max = (int) Math.pow(2, mapBit) - 1;
+ }
+
+ public MCSubChunk(long[] longArray, int mapBit) {
+ this.longArray = longArray;
+ this.mapBit = mapBit;
+ max = (int) Math.pow(2, mapBit) - 1;
+ }
+
+ public MCSubChunk(int longLength, int mapBit) {
+ this.longArray = new long[longLength];
+ this.mapBit = mapBit;
+ max = (int) Math.pow(2, mapBit) - 1;
+ }
+
+ /**
+ * 获取Long数组
+ *
+ * @return 地图数据
+ */
+ public long[] getLongArray() {
+ return longArray;
+ }
+
+ /**
+ * 获取地图存储的位大小
+ * @return 存储的位大小
+ */
+ public int getMapBit() {
+ return mapBit;
+ }
+
+ /**
+ * 设置值
+ * @param index 索引
+ * @param vel 值
+ */
+ public void set(int index, long vel) {
+ if (vel < 0 || vel > max)
+ throw new NBTException("设置的值不在范围:" + vel);
+ int longIndex = index / (64 / mapBit);
+ if (longIndex >= longArray.length)
+ throw new NBTException("索引超出范围:" + index);
+ int bitIndex = index % (64 / mapBit) * mapBit;
+ longArray[longIndex] = longArray[longIndex] & ~(max << bitIndex);
+ longArray[longIndex] = (vel << bitIndex) | longArray[longIndex];
+ }
+
+ /**
+ * 获取值
+ * @param index 索引
+ * @return 值
+ */
+ public long get(int index) {
+ int longIndex = index / (64 / mapBit);
+ if (longIndex >= longArray.length)
+ throw new NBTException("索引超出范围:" + index);
+ int bitIndex = index % (64 / mapBit) * mapBit;
+ long b = (longArray[longIndex] & (max << bitIndex)) >> bitIndex;
+ if (b < 0)
+ return 0;
+ return b;
+ }
+
+}
diff --git a/NBTUtils/src/main/nbt/CompoundTag.java b/NBTUtils/src/main/nbt/CompoundTag.java
new file mode 100644
index 0000000..188b682
--- /dev/null
+++ b/NBTUtils/src/main/nbt/CompoundTag.java
@@ -0,0 +1,107 @@
+package main.nbt;
+
+import java.util.*;
+
+import static main.nbt.TagType.*;
+
+public class CompoundTag extends LinkedHashMap {
+
+
+ public CompoundTag() {
+ }
+
+ @Override//克隆新的复合标签对象
+ public CompoundTag clone() {
+ CompoundTag tag = new CompoundTag();
+ Iterator> integer = super.entrySet().iterator();
+ while (integer.hasNext()) {
+ Map.Entry entry = integer.next();
+ tag.put(entry.getKey(), TagType.cloneTag(entry.getValue()));
+ }
+ return tag;
+ }
+
+ public ListTag getListTag(String name) {
+ return (ListTag) (Object) super.get(name);
+ }
+
+ /**
+ * 获取复合标签
+ *
+ * @param name 标签名
+ * @return 复合标签 没有返回null
+ */
+ public CompoundTag getCompoundTag(String name) {
+ return (CompoundTag) super.get(name);
+ }
+
+ /**
+ * 获取标签
+ *
+ * @param name 标签名
+ * @return 标签 没有返回null
+ */
+ public Object getTag(String name) {
+ return super.get(name);
+ }
+
+ /**
+ * 获取标签的类型
+ *
+ * @param name 标签名
+ * @return 标签类型 没有返回-1
+ */
+ public short getTagType(String name) {
+ return TagType.Object2TagType(super.get(name));
+ }
+
+ /**
+ * 设置列表标签
+ *
+ * @param name 标签名
+ * @param listTag 要设置的列表标签
+ * @return 自身
+ */
+ public CompoundTag setListTag(String name, ListTag listTag) {
+ super.put(name, listTag);
+ return this;
+ }
+
+ /**
+ * 设置复合标签
+ *
+ * @param name 标签名
+ * @param compoundTag 要设置的复合标签
+ * @return 自身
+ */
+ public CompoundTag setCompoundTag(String name, CompoundTag compoundTag) {
+ super.put(name, compoundTag);
+ return this;
+ }
+
+ /**
+ * 设置新的复合标签
+ *
+ * @param name 标签名
+ * @return 新的复合标签
+ */
+ public CompoundTag setCompoundTag(String name) {
+ CompoundTag newCompoundTag = new CompoundTag();
+ super.put(name, newCompoundTag);
+ return newCompoundTag;
+ }
+
+ /**
+ * 设置标签
+ *
+ * @param name 标签名
+ * @param data 标签对象
+ * @return 自身
+ */
+ public CompoundTag setTag(String name, Object data) {
+ super.put(name, data);
+ return this;
+ }
+
+
+}
diff --git a/NBTUtils/src/main/nbt/ListTag.java b/NBTUtils/src/main/nbt/ListTag.java
new file mode 100644
index 0000000..6e7f63b
--- /dev/null
+++ b/NBTUtils/src/main/nbt/ListTag.java
@@ -0,0 +1,73 @@
+package main.nbt;
+
+import main.exception.NBTException;
+
+import java.util.ArrayList;
+
+public class ListTag extends ArrayList