|
|
|
|
@@ -8,7 +8,7 @@ Copyright xymb@endcrystal.me
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
|
|
diff --git a/build.gradle.kts b/build.gradle.kts
|
|
|
|
|
index 7f1f4988a25a5ebac36c25c045c51eefecab6862..09f9a12da1807dd72a95378b4824d9a09f7ccbfe 100644
|
|
|
|
|
index abfd4d5faf23511284b4efac771ef79da0ef72da..0a6549d0f545e81ae95527797333f2e2da85e1f1 100644
|
|
|
|
|
--- a/build.gradle.kts
|
|
|
|
|
+++ b/build.gradle.kts
|
|
|
|
|
@@ -13,6 +13,10 @@ dependencies {
|
|
|
|
|
@@ -37,39 +37,12 @@ index f2c27e0ac65be4b75c1d86ef6fd45fdb538d96ac..00724993d0448454d14a47652b039b88
|
|
|
|
|
|
|
|
|
|
public static final class InProgressWrite {
|
|
|
|
|
public long writeCounter;
|
|
|
|
|
diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java
|
|
|
|
|
index 09fb7dcc56d5e266c8a1719c696653f644e0cfc1..2ae67b36a08688c3041635c2db04b05e295c4eea 100644
|
|
|
|
|
--- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java
|
|
|
|
|
+++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java
|
|
|
|
|
@@ -193,8 +193,9 @@ public class KaiijuConfig {
|
|
|
|
|
return builder.build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- public static List<String> regionFormatList = Arrays.asList("ANVIL");
|
|
|
|
|
+ public static List<String> regionFormatList = Arrays.asList("ANVIL", "LINEAR");
|
|
|
|
|
public static String regionFormatName = "ANVIL";
|
|
|
|
|
+ public static int regionFormatLinearCompressionLevel = 1;
|
|
|
|
|
|
|
|
|
|
private static void regionFormatSettings() {
|
|
|
|
|
regionFormatName = getString("region-format.format", regionFormatName).toUpperCase();
|
|
|
|
|
@@ -203,5 +204,11 @@ public class KaiijuConfig {
|
|
|
|
|
Bukkit.getLogger().log(Level.SEVERE, "Falling back to ANVIL region file format.");
|
|
|
|
|
regionFormatName = "ANVIL";
|
|
|
|
|
}
|
|
|
|
|
+ regionFormatLinearCompressionLevel = getInt("region-format.linear.compression-level", regionFormatLinearCompressionLevel);
|
|
|
|
|
+ if (regionFormatLinearCompressionLevel > 23 || regionFormatLinearCompressionLevel < 1) {
|
|
|
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Linear region compression level should be between 1 and 22 in kaiiju.yml: " + regionFormatLinearCompressionLevel);
|
|
|
|
|
+ Bukkit.getLogger().log(Level.SEVERE, "Falling back to compression level 1.");
|
|
|
|
|
+ regionFormatLinearCompressionLevel = 1;
|
|
|
|
|
+ }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFile.java b/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFile.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..70d53d80f474eb80acc93275ad6bf1da98ea8ae7
|
|
|
|
|
index 0000000000000000000000000000000000000000..249303116d3cfadd078ebf0ae6e44bf99eed6a47
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFile.java
|
|
|
|
|
@@ -0,0 +1,32 @@
|
|
|
|
|
@@ -0,0 +1,31 @@
|
|
|
|
|
+package dev.kaiijumc.kaiiju.region;
|
|
|
|
|
+
|
|
|
|
|
+import net.minecraft.nbt.CompoundTag;
|
|
|
|
|
@@ -83,64 +56,55 @@ index 0000000000000000000000000000000000000000..70d53d80f474eb80acc93275ad6bf1da
|
|
|
|
|
+import java.util.concurrent.locks.ReentrantLock;
|
|
|
|
|
+
|
|
|
|
|
+public interface AbstractRegionFile {
|
|
|
|
|
+ boolean doesChunkExist(ChunkPos pos) throws Exception;
|
|
|
|
|
+ void flush() throws IOException;
|
|
|
|
|
+ void setStatus(int x, int z, ChunkStatus status);
|
|
|
|
|
+ void clear(ChunkPos pos) throws IOException;
|
|
|
|
|
+ boolean hasChunk(ChunkPos pos);
|
|
|
|
|
+ void close() throws IOException;
|
|
|
|
|
+ void setStatus(int x, int z, ChunkStatus status);
|
|
|
|
|
+ void setOversized(int x, int z, boolean b) throws IOException;
|
|
|
|
|
+
|
|
|
|
|
+ boolean hasChunk(ChunkPos pos);
|
|
|
|
|
+ boolean doesChunkExist(ChunkPos pos) throws Exception;
|
|
|
|
|
+ boolean isOversized(int x, int z);
|
|
|
|
|
+ boolean recalculateHeader() throws IOException;
|
|
|
|
|
+
|
|
|
|
|
+ DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException;
|
|
|
|
|
+ DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException;
|
|
|
|
|
+ ChunkStatus getStatusIfCached(int x, int z);
|
|
|
|
|
+
|
|
|
|
|
+ Path getRegionFile();
|
|
|
|
|
+ ReentrantLock getFileLock();
|
|
|
|
|
+
|
|
|
|
|
+ void setOversized(int x, int z, boolean b) throws IOException;
|
|
|
|
|
+ CompoundTag getOversizedData(int x, int z) throws IOException;
|
|
|
|
|
+ boolean isOversized(int x, int z);
|
|
|
|
|
+ boolean recalculateHeader() throws IOException;
|
|
|
|
|
+ ChunkStatus getStatusIfCached(int x, int z);
|
|
|
|
|
+ ReentrantLock getFileLock();
|
|
|
|
|
+ Path getRegionFile();
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFileFactory.java b/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFileFactory.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..0b84b1aa4b822bdcb783285e8ea99a4f25dbbaa3
|
|
|
|
|
index 0000000000000000000000000000000000000000..67991815a0ff9235a846ca8db817c90f094c4a58
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/dev/kaiijumc/kaiiju/region/AbstractRegionFileFactory.java
|
|
|
|
|
@@ -0,0 +1,38 @@
|
|
|
|
|
@@ -0,0 +1,30 @@
|
|
|
|
|
+package dev.kaiijumc.kaiiju.region;
|
|
|
|
|
+
|
|
|
|
|
+import net.minecraft.world.level.chunk.storage.RegionFile;
|
|
|
|
|
+import net.minecraft.world.level.chunk.storage.RegionFileVersion;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.nio.file.Path;
|
|
|
|
|
+
|
|
|
|
|
+public class AbstractRegionFileFactory {
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(Path file, Path directory, boolean dsync) throws IOException {
|
|
|
|
|
+ return getAbstractRegionFile(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
|
|
|
|
|
+
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(int linearCompression, int linearInternalCompression, Path file, Path directory, boolean dsync) throws IOException {
|
|
|
|
|
+ return getAbstractRegionFile(linearCompression, linearInternalCompression, file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException {
|
|
|
|
|
+ return getAbstractRegionFile(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader);
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(int linearCompression, int linearInternalCompression, Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException {
|
|
|
|
|
+ return getAbstractRegionFile(linearCompression, linearInternalCompression, file, directory, RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(File file, File directory, boolean dsync) throws IOException {
|
|
|
|
|
+ return getAbstractRegionFile(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync);
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(int linearCompression, int linearInternalCompression, Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
|
|
|
|
|
+ return getAbstractRegionFile(linearCompression, linearInternalCompression, file, directory, outputChunkStreamVersion, dsync, false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(File file, File directory, boolean dsync, boolean canRecalcHeader) throws IOException {
|
|
|
|
|
+ return getAbstractRegionFile(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
|
|
|
|
|
+ return getAbstractRegionFile(file, directory, outputChunkStreamVersion, dsync, false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException {
|
|
|
|
|
+ if (dev.kaiijumc.kaiiju.KaiijuConfig.regionFormatName.equals("LINEAR")) {
|
|
|
|
|
+ return new LinearRegionFile(file, directory, outputChunkStreamVersion, dsync, canRecalcHeader);
|
|
|
|
|
+ public static AbstractRegionFile getAbstractRegionFile(int linearCompression, int linearInternalCompression, Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException {
|
|
|
|
|
+ if (file.toString().endsWith(".linear")) {
|
|
|
|
|
+ return new LinearRegionFile(file, linearCompression, linearInternalCompression);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return new RegionFile(file, directory, outputChunkStreamVersion, dsync, canRecalcHeader);
|
|
|
|
|
+ }
|
|
|
|
|
@@ -148,24 +112,19 @@ index 0000000000000000000000000000000000000000..0b84b1aa4b822bdcb783285e8ea99a4f
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFile.java b/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFile.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd493de2fcf5
|
|
|
|
|
index 0000000000000000000000000000000000000000..df2edc187480aef6af1cbeb82824984e31672068
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFile.java
|
|
|
|
|
@@ -0,0 +1,339 @@
|
|
|
|
|
@@ -0,0 +1,325 @@
|
|
|
|
|
+package dev.kaiijumc.kaiiju.region;
|
|
|
|
|
+
|
|
|
|
|
+import com.github.luben.zstd.ZstdInputStream;
|
|
|
|
|
+import com.github.luben.zstd.Zstd;import com.github.luben.zstd.ZstdInputStream;
|
|
|
|
|
+import com.github.luben.zstd.ZstdOutputStream;
|
|
|
|
|
+import com.mojang.logging.LogUtils;
|
|
|
|
|
+import dev.kaiijumc.kaiiju.KaiijuConfig;
|
|
|
|
|
+import net.jpountz.lz4.LZ4Compressor;
|
|
|
|
|
+import net.jpountz.lz4.LZ4Factory;
|
|
|
|
|
+import net.jpountz.lz4.LZ4FastDecompressor;
|
|
|
|
|
+import net.jpountz.xxhash.XXHashFactory;
|
|
|
|
|
+import net.minecraft.nbt.CompoundTag;
|
|
|
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
|
|
|
+import net.minecraft.world.level.chunk.ChunkStatus;
|
|
|
|
|
+import net.minecraft.world.level.chunk.storage.RegionFileVersion;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Nullable;
|
|
|
|
|
@@ -175,6 +134,7 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+import java.nio.file.Path;
|
|
|
|
|
+import java.nio.file.StandardCopyOption;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+import java.util.concurrent.locks.ReentrantLock;
|
|
|
|
|
+
|
|
|
|
|
+public class LinearRegionFile extends Thread implements AbstractRegionFile {
|
|
|
|
|
@@ -182,13 +142,17 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ private final byte[][] buffer = new byte[32*32][];
|
|
|
|
|
+ private final int[] bufferUncompressedSize = new int[32*32];
|
|
|
|
|
+
|
|
|
|
|
+ private final ChunkStatus[] statuses = new ChunkStatus[32 * 32];
|
|
|
|
|
+ private boolean markedToSave = false;
|
|
|
|
|
+ private final Object markedToSaveLock = new Object();
|
|
|
|
|
+ private final ChunkStatus[] statuses = new ChunkStatus[32 * 32];
|
|
|
|
|
+
|
|
|
|
|
+ private boolean markedToSave = false;
|
|
|
|
|
+ private boolean close = false;
|
|
|
|
|
+ public Path regionFile;
|
|
|
|
|
+
|
|
|
|
|
+ public final ReentrantLock fileLock = new ReentrantLock(true);
|
|
|
|
|
+ final int COMPRESSION_LEVEL = KaiijuConfig.regionFormatLinearCompressionLevel;
|
|
|
|
|
+ public Path regionFile;
|
|
|
|
|
+
|
|
|
|
|
+ private final int compressionLevel;
|
|
|
|
|
+ private final int internalCompressionLevel;
|
|
|
|
|
+
|
|
|
|
|
+ public Path getRegionFile() {
|
|
|
|
|
+ return this.regionFile;
|
|
|
|
|
@@ -198,13 +162,13 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ return this.fileLock;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public LinearRegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException {
|
|
|
|
|
+ public LinearRegionFile(Path file, int compression, int internalCompression) throws IOException {
|
|
|
|
|
+ this.regionFile = file;
|
|
|
|
|
+ this.compressionLevel = compression;
|
|
|
|
|
+ this.internalCompressionLevel = internalCompression;
|
|
|
|
|
+ File regionFile = new File(this.regionFile.toString());
|
|
|
|
|
+
|
|
|
|
|
+ LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor();
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0 ; i < 32 * 32 ; i++)
|
|
|
|
|
+ for (int i = 0; i < 32 * 32; i++)
|
|
|
|
|
+ this.bufferUncompressedSize[i] = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if(regionFile.canRead()) {
|
|
|
|
|
@@ -220,17 +184,13 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ long superBlock = rawDataStream.readLong();
|
|
|
|
|
+
|
|
|
|
|
+ if (superBlock != SUPERBLOCK) {
|
|
|
|
|
+ System.out.println(file.toString());
|
|
|
|
|
+ System.out.println("SUPERBLOCK INVALID!");
|
|
|
|
|
+ return;
|
|
|
|
|
+ throw new IOException("SUPERBLOCK VERSION! " + file);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ byte version = rawDataStream.readByte();
|
|
|
|
|
+
|
|
|
|
|
+ if (version != VERSION) {
|
|
|
|
|
+ System.out.println(file.toString());
|
|
|
|
|
+ System.out.println("VERSION INVALID!");
|
|
|
|
|
+ return;
|
|
|
|
|
+ throw new IOException("INVALID VERSION! " + file);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ rawDataStream.readLong(); // newestTimestamp
|
|
|
|
|
@@ -239,7 +199,7 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ int dataCount = rawDataStream.readInt();
|
|
|
|
|
+
|
|
|
|
|
+ if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) {
|
|
|
|
|
+ throw new IOException("File length invalid " + this.regionFile + " " + String.valueOf(fileLength) + " " + String.valueOf(HEADER_SIZE + dataCount + FOOTER_SIZE));
|
|
|
|
|
+ throw new IOException("File length invalid " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ rawDataStream.readLong(); // Data Hash
|
|
|
|
|
@@ -267,11 +227,10 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ byte[] b = new byte[size];
|
|
|
|
|
+ dataStream.readFully(b, 0, size);
|
|
|
|
|
+
|
|
|
|
|
+ int maxCompressedLength = compressor.maxCompressedLength(size);
|
|
|
|
|
+ int maxCompressedLength = (int)Zstd.compressBound(size);
|
|
|
|
|
+ byte[] compressed = new byte[maxCompressedLength];
|
|
|
|
|
+ int compressedLength = compressor.compress(b, 0, size, compressed, 0, maxCompressedLength);
|
|
|
|
|
+ b = new byte[compressedLength];
|
|
|
|
|
+ System.arraycopy(compressed, 0, b, 0, compressedLength);
|
|
|
|
|
+ long compressedLength = Zstd.compress(compressed, b, this.internalCompressionLevel);
|
|
|
|
|
+ b = Arrays.copyOfRange(compressed, 0, (int) compressedLength);
|
|
|
|
|
+
|
|
|
|
|
+ this.buffer[i] = b;
|
|
|
|
|
+ this.bufferUncompressedSize[i] = size;
|
|
|
|
|
@@ -304,7 +263,7 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ try {
|
|
|
|
|
+ flush();
|
|
|
|
|
+ } catch(IOException ex) {
|
|
|
|
|
+ LOGGER.error("Region file " + this.regionFile.toAbsolutePath() + " flush failed");
|
|
|
|
|
+ LOGGER.error("Region file " + this.regionFile.toAbsolutePath() + " flush failed", ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for(int i = 0 ; i < 100 ; i++) {
|
|
|
|
|
@@ -334,7 +293,7 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ FileOutputStream fileStream = new FileOutputStream(tempFile);
|
|
|
|
|
+
|
|
|
|
|
+ ByteArrayOutputStream zstdByteArray = new ByteArrayOutputStream();
|
|
|
|
|
+ ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, COMPRESSION_LEVEL);
|
|
|
|
|
+ ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, this.compressionLevel);
|
|
|
|
|
+ zstdStream.setChecksum(true);
|
|
|
|
|
+ DataOutputStream zstdDataStream = new DataOutputStream(zstdStream);
|
|
|
|
|
+ DataOutputStream dataStream = new DataOutputStream(fileStream);
|
|
|
|
|
@@ -342,16 +301,14 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ dataStream.writeLong(SUPERBLOCK);
|
|
|
|
|
+ dataStream.writeByte(VERSION);
|
|
|
|
|
+ dataStream.writeLong(timestamp);
|
|
|
|
|
+ dataStream.writeByte(COMPRESSION_LEVEL);
|
|
|
|
|
+ dataStream.writeByte(this.compressionLevel);
|
|
|
|
|
+
|
|
|
|
|
+ LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor();
|
|
|
|
|
+
|
|
|
|
|
+ ArrayList<byte[]> byteBuffers = new ArrayList<byte[]>();
|
|
|
|
|
+ ArrayList<byte[]> byteBuffers = new ArrayList<>();
|
|
|
|
|
+ for(int i = 0 ; i < 32 * 32 ; i++) {
|
|
|
|
|
+ if(this.bufferUncompressedSize[i] != 0) {
|
|
|
|
|
+ chunkCount += 1;
|
|
|
|
|
+ byte[] content = new byte[bufferUncompressedSize[i]];
|
|
|
|
|
+ decompressor.decompress(buffer[i], 0, content, 0, bufferUncompressedSize[i]);
|
|
|
|
|
+ Zstd.decompress(content, buffer[i]);
|
|
|
|
|
+
|
|
|
|
|
+ byteBuffers.add(content);
|
|
|
|
|
+ } else byteBuffers.add(null);
|
|
|
|
|
@@ -387,27 +344,20 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public synchronized void write(ChunkPos pos, ByteBuffer buffer) {
|
|
|
|
|
+ LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor();
|
|
|
|
|
+ try {
|
|
|
|
|
+ byte[] b = toByteArray(new ByteArrayInputStream(buffer.array()));
|
|
|
|
|
+ int uncompressedSize = b.length;
|
|
|
|
|
+
|
|
|
|
|
+ int maxCompressedLength = compressor.maxCompressedLength(b.length);
|
|
|
|
|
+ byte[] compressed = new byte[maxCompressedLength];
|
|
|
|
|
+ int compressedLength = compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength);
|
|
|
|
|
+ b = new byte[compressedLength];
|
|
|
|
|
+ System.arraycopy(compressed, 0, b, 0, compressedLength);
|
|
|
|
|
+
|
|
|
|
|
+ this.buffer[getChunkIndex(pos.x, pos.z)] = b;
|
|
|
|
|
+ byte[] compressed = Zstd.compress(b, this.internalCompressionLevel);
|
|
|
|
|
+ this.buffer[getChunkIndex(pos.x, pos.z)] = compressed;
|
|
|
|
|
+ this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize;
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ LOGGER.error("Chunk write IOException " + e.toString() + " " + this.regionFile);
|
|
|
|
|
+ LOGGER.error("Chunk write IOException " + e + " " + this.regionFile);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ markToSave();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException {
|
|
|
|
|
+ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) {
|
|
|
|
|
+ return new DataOutputStream(new BufferedOutputStream(new LinearRegionFile.ChunkBuffer(pos)));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
@@ -439,11 +389,11 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Nullable
|
|
|
|
|
+ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException {
|
|
|
|
|
+ if(this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) {
|
|
|
|
|
+ LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor();
|
|
|
|
|
+ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) {
|
|
|
|
|
+ if (this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) {
|
|
|
|
|
+ byte[] compressedData = this.buffer[getChunkIndex(pos.x, pos.z)];
|
|
|
|
|
+ byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]];
|
|
|
|
|
+ decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]);
|
|
|
|
|
+ Zstd.decompress(content, compressedData);
|
|
|
|
|
+ return new DataInputStream(new ByteArrayInputStream(content));
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
@@ -469,7 +419,7 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ try {
|
|
|
|
|
+ flush();
|
|
|
|
|
+ } catch(IOException e) {
|
|
|
|
|
+ throw new IOException("Region flush IOException " + e.toString() + " " + this.regionFile);
|
|
|
|
|
+ throw new IOException("Region flush IOException " + e + " " + this.regionFile);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
@@ -477,7 +427,7 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ return (x & 31) + (z & 31) * 32;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public boolean recalculateHeader() throws IOException {
|
|
|
|
|
+ public boolean recalculateHeader() {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
@@ -492,7 +442,7 @@ index 0000000000000000000000000000000000000000..5902bc0cc13d1a77eb547efc7579fd49
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java
|
|
|
|
|
index a08cde4eefe879adcee7c4118bc38f98c5097ed0..77ce70697f2a634927fd02f2a103c91a7bf09d2a 100644
|
|
|
|
|
index a08cde4eefe879adcee7c4118bc38f98c5097ed0..1cfc20b7496f93aff1d6c2387dc5bb8bacab8dc8 100644
|
|
|
|
|
--- a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java
|
|
|
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java
|
|
|
|
|
@@ -811,7 +811,7 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread {
|
|
|
|
|
@@ -558,31 +508,50 @@ index a08cde4eefe879adcee7c4118bc38f98c5097ed0..77ce70697f2a634927fd02f2a103c91a
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1287,6 +1287,12 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread {
|
|
|
|
|
} catch (final ThreadDeath thr) {
|
|
|
|
|
throw thr;
|
|
|
|
|
} catch (final Throwable thr) {
|
|
|
|
|
+ // Kaiiju start - Linear region format
|
|
|
|
|
+ if (dev.kaiijumc.kaiiju.KaiijuConfig.regionFormatName.equals("LINEAR")) {
|
|
|
|
|
+ failedWrite = thr instanceof IOException;
|
|
|
|
|
+ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
if (thr instanceof RegionFileStorage.RegionFileSizeException) {
|
|
|
|
|
final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024);
|
|
|
|
|
LOGGER.error("Chunk at (" + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk.");
|
|
|
|
|
@@ -1294,6 +1300,7 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread {
|
|
|
|
|
failedWrite = thr instanceof IOException;
|
|
|
|
|
LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr);
|
|
|
|
|
}
|
|
|
|
|
+ } // Kaiiju
|
|
|
|
|
}
|
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
|
|
|
|
|
index 95cac7edae8ac64811fc6a2f6b97dd4a0fceb0b0..e340878efb93499acd7fdf83a66ead9b53210a76 100644
|
|
|
|
|
--- a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
|
|
|
|
|
+++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
|
|
|
|
|
@@ -83,9 +83,15 @@ public class ThreadedWorldUpgrader {
|
|
|
|
|
}
|
|
|
|
|
LOGGER.info("Found " + regionFiles.length + " regionfiles to convert");
|
|
|
|
|
LOGGER.info("Starting conversion now for world " + this.worldName);
|
|
|
|
|
-
|
|
|
|
|
+ // Kaiiju start
|
|
|
|
|
+ net.minecraft.server.level.ServerLevel level = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorld(worldName)).getHandle();
|
|
|
|
|
+ String formatName = level.kaiijuConfig.regionFormatName;
|
|
|
|
|
+ int linearCompression = level.kaiijuConfig.regionFormatLinearCompressionLevel;
|
|
|
|
|
+ int internalLinearCompression = level.kaiijuConfig.regionFormatLinearInternalCompressionLevel;
|
|
|
|
|
+ LOGGER.info("Using format " + formatName + " (" + linearCompression + ")");
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
final WorldInfo info = new WorldInfo(() -> worldPersistentData,
|
|
|
|
|
- new ChunkStorage(regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey);
|
|
|
|
|
+ new ChunkStorage(formatName, linearCompression, internalLinearCompression, regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey); // Kaiiju
|
|
|
|
|
|
|
|
|
|
long expectedChunks = (long)regionFiles.length * (32L * 32L);
|
|
|
|
|
|
|
|
|
|
final boolean finalFailWrite = failedWrite;
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
|
|
|
index 75965afd7b4bed23a5ecf618c7f91ff5e7ffd92f..2158c576aa47ef618b7602c2f4e351b7beb023b3 100644
|
|
|
|
|
index 75965afd7b4bed23a5ecf618c7f91ff5e7ffd92f..3dd88803c5897ac856c107e7f54c3b73cba5adf8 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
|
|
|
@@ -291,7 +291,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
|
// Paper end
|
|
|
|
|
|
|
|
|
|
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
|
|
|
|
|
- super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
|
|
|
|
|
+ super(world.getLevel().kaiijuConfig.regionFormatName, world.getLevel().kaiijuConfig.regionFormatLinearCompressionLevel, world.getLevel().kaiijuConfig.regionFormatLinearInternalCompressionLevel, session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); // Kaiiju
|
|
|
|
|
// Paper - rewrite chunk system
|
|
|
|
|
this.tickingGenerated = new AtomicInteger();
|
|
|
|
|
this.playerMap = new PlayerMap();
|
|
|
|
|
@@ -336,7 +336,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
|
this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), null, null); // Paper - rewrite chunk system
|
|
|
|
|
this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor);
|
|
|
|
|
this.overworldDataStorage = persistentStateManagerFactory;
|
|
|
|
|
- this.poiManager = new PoiManager(path.resolve("poi"), dataFixer, dsync, iregistrycustom, world);
|
|
|
|
|
+ this.poiManager = new PoiManager(this.level.kaiijuConfig.regionFormatName, this.level.kaiijuConfig.regionFormatLinearCompressionLevel, this.level.kaiijuConfig.regionFormatLinearInternalCompressionLevel, path.resolve("poi"), dataFixer, dsync, iregistrycustom, world); // Kaiiju
|
|
|
|
|
this.setViewDistance(viewDistance);
|
|
|
|
|
// Paper start
|
|
|
|
|
this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
|
|
|
|
|
@@ -950,13 +950,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
|
|
|
|
|
|
// Paper start - chunk status cache "api"
|
|
|
|
|
@@ -608,50 +577,119 @@ index 75965afd7b4bed23a5ecf618c7f91ff5e7ffd92f..2158c576aa47ef618b7602c2f4e351b7
|
|
|
|
|
|
|
|
|
|
regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound));
|
|
|
|
|
}
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
|
|
|
index a5655ebb233f1e1e1dd7f79fdd948020478928fc..92430e71f64d88352838ee9cf0f4d22c050e7acc 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
|
|
|
@@ -390,9 +390,8 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
|
|
|
private final EntityRegionFileStorage entityStorage;
|
|
|
|
|
|
|
|
|
|
private static final class EntityRegionFileStorage extends net.minecraft.world.level.chunk.storage.RegionFileStorage {
|
|
|
|
|
-
|
|
|
|
|
- public EntityRegionFileStorage(Path directory, boolean dsync) {
|
|
|
|
|
- super(directory, dsync);
|
|
|
|
|
+ public EntityRegionFileStorage(String format, int linearCompression, int linearInternalCompression, Path directory, boolean dsync) { // Kaiiju
|
|
|
|
|
+ super(format, linearCompression, linearInternalCompression, directory, dsync); // Kaiiju
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void write(ChunkPos pos, net.minecraft.nbt.CompoundTag nbt) throws IOException {
|
|
|
|
|
@@ -583,7 +582,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
|
|
|
// CraftBukkit end
|
|
|
|
|
boolean flag2 = minecraftserver.forceSynchronousWrites();
|
|
|
|
|
DataFixer datafixer = minecraftserver.getFixerUpper();
|
|
|
|
|
- this.entityStorage = new EntityRegionFileStorage(convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver);
|
|
|
|
|
+ this.entityStorage = new EntityRegionFileStorage(this.getLevel().kaiijuConfig.regionFormatName, this.getLevel().kaiijuConfig.regionFormatLinearCompressionLevel, this.getLevel().kaiijuConfig.regionFormatLinearInternalCompressionLevel, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver); // Kaiiju
|
|
|
|
|
|
|
|
|
|
// this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper // Paper - rewrite chunk system
|
|
|
|
|
StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager();
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
|
|
|
|
|
index 759b125cc1251b9b4f1f443c9f70c482ef5b32f8..aca60733c87f7f95b5719ded24eb8cf697f9d83e 100644
|
|
|
|
|
index 759b125cc1251b9b4f1f443c9f70c482ef5b32f8..761293b17cf12e28487034508906702bae2a3feb 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
|
|
|
|
|
@@ -61,10 +61,17 @@ public class WorldUpgrader {
|
|
|
|
|
@@ -61,7 +61,7 @@ public class WorldUpgrader {
|
|
|
|
|
private volatile int skipped;
|
|
|
|
|
private final Object2FloatMap<ResourceKey<LevelStem>> progressMap = Object2FloatMaps.synchronize(new Object2FloatOpenCustomHashMap(Util.identityStrategy())); // CraftBukkit
|
|
|
|
|
private volatile Component status = Component.translatable("optimizeWorld.stage.counting");
|
|
|
|
|
- public static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
|
|
|
|
|
+ public static Pattern REGEX; // Kaiiju
|
|
|
|
|
+ public static Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.(linear | mca)$"); // Kaiiju
|
|
|
|
|
private final DimensionDataStorage overworldDataStorage;
|
|
|
|
|
|
|
|
|
|
public WorldUpgrader(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, Registry<LevelStem> dimensionOptionsRegistry, boolean eraseCache) {
|
|
|
|
|
+ // Kaiiju start
|
|
|
|
|
+ if (dev.kaiijumc.kaiiju.KaiijuConfig.regionFormatName.equals("LINEAR")) {
|
|
|
|
|
+ this.REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.linear$");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
|
|
|
|
|
+ }
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
this.dimensions = dimensionOptionsRegistry;
|
|
|
|
|
this.levels = (Set) dimensionOptionsRegistry.registryKeySet().stream().collect(Collectors.toUnmodifiableSet()); // CraftBukkit
|
|
|
|
|
this.eraseCache = eraseCache;
|
|
|
|
|
@@ -235,6 +242,11 @@ public class WorldUpgrader {
|
|
|
|
|
@@ -115,8 +115,14 @@ public class WorldUpgrader {
|
|
|
|
|
while (iterator1.hasNext()) {
|
|
|
|
|
ResourceKey<LevelStem> resourcekey1 = (ResourceKey) iterator1.next(); // CraftBukkit
|
|
|
|
|
Path path = this.levelStorage.getDimensionPath((ResourceKey) null); // CraftBukkit
|
|
|
|
|
-
|
|
|
|
|
- builder1.put(resourcekey1, new ChunkStorage(path.resolve("region"), this.dataFixer, true));
|
|
|
|
|
+ // Kaiiju start
|
|
|
|
|
+ String worldName = this.levelStorage.getLevelId();
|
|
|
|
|
+ net.minecraft.server.level.ServerLevel level = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorld(worldName)).getHandle();
|
|
|
|
|
+ String formatName = level.kaiijuConfig.regionFormatName;
|
|
|
|
|
+ int linearCompression = level.kaiijuConfig.regionFormatLinearCompressionLevel;
|
|
|
|
|
+ int linearInternalCompression = level.kaiijuConfig.regionFormatLinearInternalCompressionLevel;
|
|
|
|
|
+ builder1.put(resourcekey1, new ChunkStorage(formatName, linearCompression, linearInternalCompression, path.resolve("region"), this.dataFixer, true));
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImmutableMap<ResourceKey<LevelStem>, ChunkStorage> immutablemap1 = builder1.build(); // CraftBukkit
|
|
|
|
|
@@ -235,7 +241,7 @@ public class WorldUpgrader {
|
|
|
|
|
File file = this.levelStorage.getDimensionPath((ResourceKey) null).toFile(); // CraftBukkit
|
|
|
|
|
File file1 = new File(file, "region");
|
|
|
|
|
File[] afile = file1.listFiles((file2, s) -> {
|
|
|
|
|
+ // Kaiiju start
|
|
|
|
|
+ if (dev.kaiijumc.kaiiju.KaiijuConfig.regionFormatName.equals("LINEAR")) {
|
|
|
|
|
+ return s.endsWith(".linear");
|
|
|
|
|
+ }
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
return s.endsWith(".mca");
|
|
|
|
|
- return s.endsWith(".mca");
|
|
|
|
|
+ return s.endsWith(".mca") || s.endsWith(".linear"); // Kaiiju
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -254,7 +266,7 @@ public class WorldUpgrader {
|
|
|
|
|
if (afile == null) {
|
|
|
|
|
@@ -254,7 +260,13 @@ public class WorldUpgrader {
|
|
|
|
|
int l = Integer.parseInt(matcher.group(2)) << 5;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
- RegionFile regionfile = new RegionFile(file2.toPath(), file1.toPath(), true);
|
|
|
|
|
+ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = dev.kaiijumc.kaiiju.region.AbstractRegionFileFactory.getAbstractRegionFile(file2.toPath(), file1.toPath(), true); // Kaiiju
|
|
|
|
|
+ // Kaiiju start
|
|
|
|
|
+ String worldName = this.levelStorage.getLevelId();
|
|
|
|
|
+ net.minecraft.server.level.ServerLevel level = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorld(worldName)).getHandle();
|
|
|
|
|
+ int linearCompression = level.kaiijuConfig.regionFormatLinearCompressionLevel;
|
|
|
|
|
+ int linearInternalCompression = level.kaiijuConfig.regionFormatLinearInternalCompressionLevel;
|
|
|
|
|
+ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile = dev.kaiijumc.kaiiju.region.AbstractRegionFileFactory.getAbstractRegionFile(linearCompression, linearInternalCompression, file2.toPath(), file1.toPath(), true);
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
for (int i1 = 0; i1 < 32; ++i1) {
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
|
|
|
|
index 8950b220b9a3512cd4667beb7bdec0e82e07edc6..ca93af37207a78e41b6d20333638bec32c11c416 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
|
|
|
|
|
@@ -57,8 +57,8 @@ public class PoiManager extends SectionStorage<PoiSection> {
|
|
|
|
|
// Paper end - rewrite chunk system
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) {
|
|
|
|
|
- super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world);
|
|
|
|
|
+ public PoiManager(String formatName, int linearCompression, int linearInternalCompression, Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) { // Kaiiju
|
|
|
|
|
+ super(formatName, linearCompression, linearInternalCompression, path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world); // Kaiiju
|
|
|
|
|
this.world = (net.minecraft.server.level.ServerLevel)world; // Paper - rewrite chunk system
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
|
|
|
|
index 5a425be023d77f0370d102dfb52427147849ac1a..527384a43f5c512273cce0d16a3bc887e24357c2 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
|
|
|
|
@@ -37,11 +37,12 @@ public class ChunkStorage implements AutoCloseable {
|
|
|
|
|
public final RegionFileStorage regionFileCache;
|
|
|
|
|
// Paper end - async chunk loading
|
|
|
|
|
|
|
|
|
|
- public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) {
|
|
|
|
|
+ public ChunkStorage(String format, int linearCompression, int linearInternalCompression, Path directory, DataFixer dataFixer, boolean dsync) { // Kaiiju
|
|
|
|
|
this.fixerUpper = dataFixer;
|
|
|
|
|
+
|
|
|
|
|
// Paper start - async chunk io
|
|
|
|
|
// remove IO worker
|
|
|
|
|
- this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - nuke IOWorker // Paper
|
|
|
|
|
+ this.regionFileCache = new RegionFileStorage(format, linearCompression, linearInternalCompression, directory, dsync, true); // Paper - nuke IOWorker // Paper // Kaiiju
|
|
|
|
|
// Paper end - async chunk io
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
|
|
|
|
|
index dcfe090c269d4cbcc2eb1b6f85392848bb34656c..d42c320179ae055b8675d1ce6ce1788ecafb8e9d 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
|
|
|
|
|
@@ -714,39 +752,51 @@ index dcfe090c269d4cbcc2eb1b6f85392848bb34656c..d42c320179ae055b8675d1ce6ce1788e
|
|
|
|
|
try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new InflaterInputStream(Files.newInputStream(file))))) {
|
|
|
|
|
return NbtIo.read((java.io.DataInput) out);
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
|
|
|
|
index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da747cf30c 100644
|
|
|
|
|
index bd502ca721de0cab438d995efa00ad0554c0d2fe..ad784bc53d5c9a4abb944ce21e5bb81b939ecbea 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
|
|
|
|
|
@@ -21,8 +21,9 @@ import net.minecraft.world.level.ChunkPos;
|
|
|
|
|
public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -22,17 +22,27 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
|
|
|
|
|
public static final String ANVIL_EXTENSION = ".mca";
|
|
|
|
|
+ public static final String LINEAR_EXTENSION = ".linear"; // Kaiiju
|
|
|
|
|
private static final int MAX_CACHE_SIZE = 256;
|
|
|
|
|
- public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap();
|
|
|
|
|
+ public final Long2ObjectLinkedOpenHashMap<dev.kaiijumc.kaiiju.region.AbstractRegionFile> regionCache = new Long2ObjectLinkedOpenHashMap(); // Kaiiju
|
|
|
|
|
private final Path folder;
|
|
|
|
|
private final boolean sync;
|
|
|
|
|
+ // Kaiiju start - Per world chunk format
|
|
|
|
|
+ public final String format;
|
|
|
|
|
+ public final int linearCompression;
|
|
|
|
|
+ public final int linearInternalCompression;
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
|
|
|
|
|
@@ -42,9 +43,17 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
private final boolean isChunkData; // Paper
|
|
|
|
|
|
|
|
|
|
- protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor
|
|
|
|
|
+ protected RegionFileStorage(String format, int linearCompression, int linearInternalCompression, Path directory, boolean dsync) { // Paper - protected constructor
|
|
|
|
|
// Paper start - add isChunkData param
|
|
|
|
|
- this(directory, dsync, false);
|
|
|
|
|
+ this(format, linearCompression, linearInternalCompression, directory, dsync, false);
|
|
|
|
|
}
|
|
|
|
|
- RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) {
|
|
|
|
|
+ RegionFileStorage(String format, int linearCompression, int linearInternalCompression, Path directory, boolean dsync, boolean isChunkData) { // Kaiiju
|
|
|
|
|
+ // Kaiiju start
|
|
|
|
|
+ this.format = format;
|
|
|
|
|
+ this.linearCompression = linearCompression;
|
|
|
|
|
+ this.linearInternalCompression = linearInternalCompression;
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
this.isChunkData = isChunkData;
|
|
|
|
|
// Paper end - add isChunkData param
|
|
|
|
|
this.folder = directory;
|
|
|
|
|
@@ -42,7 +52,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
// Paper start
|
|
|
|
|
public static @Nullable ChunkPos getRegionFileCoordinates(Path file) {
|
|
|
|
|
String fileName = file.getFileName().toString();
|
|
|
|
|
+ // Kaiiju start
|
|
|
|
|
+ if (dev.kaiijumc.kaiiju.KaiijuConfig.regionFormatName.equals("LINEAR")) {
|
|
|
|
|
+ if (!fileName.startsWith("r.") || !fileName.endsWith(".linear")) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) {
|
|
|
|
|
- if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) {
|
|
|
|
|
+ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca") || !fileName.endsWith(".linear")) { // Kaiiju
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
+ } // Kaiiju
|
|
|
|
|
|
|
|
|
|
String[] split = fileName.split("\\.");
|
|
|
|
|
|
|
|
|
|
@@ -62,49 +71,57 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -62,49 +72,66 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -792,18 +842,28 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
Path path = this.folder;
|
|
|
|
|
int j = chunkcoordintpair.getRegionX();
|
|
|
|
|
- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change
|
|
|
|
|
+ // Kaiiju start
|
|
|
|
|
- if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit
|
|
|
|
|
- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
|
|
|
|
|
+ // Kaiiju start - Polyglot
|
|
|
|
|
+ //Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change
|
|
|
|
|
+ Path path1;
|
|
|
|
|
+ if (dev.kaiijumc.kaiiju.KaiijuConfig.regionFormatName.equals("LINEAR")) {
|
|
|
|
|
+ path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".linear");
|
|
|
|
|
+ if (existingOnly) {
|
|
|
|
|
+ Path anvil = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
|
|
|
|
|
+ Path linear = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".linear");
|
|
|
|
|
+ if (java.nio.file.Files.exists(anvil)) path1 = anvil;
|
|
|
|
|
+ else if (java.nio.file.Files.exists(linear)) path1 = linear;
|
|
|
|
|
+ else return null;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
|
|
|
|
|
+ String extension = switch (this.format) {
|
|
|
|
|
+ case "LINEAR" -> "linear";
|
|
|
|
|
+ default -> "mca";
|
|
|
|
|
+ };
|
|
|
|
|
+ path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + "." + extension);
|
|
|
|
|
+ }
|
|
|
|
|
+ //if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit
|
|
|
|
|
+ // Kaiiju end
|
|
|
|
|
if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit
|
|
|
|
|
- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
|
|
|
|
|
+ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile1 = dev.kaiijumc.kaiiju.region.AbstractRegionFileFactory.getAbstractRegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header // Kaiiju
|
|
|
|
|
+
|
|
|
|
|
+ dev.kaiijumc.kaiiju.region.AbstractRegionFile regionfile1 = dev.kaiijumc.kaiiju.region.AbstractRegionFileFactory.getAbstractRegionFile(this.linearCompression, this.linearInternalCompression, path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header // Kaiiju
|
|
|
|
|
|
|
|
|
|
this.regionCache.putAndMoveToFirst(i, regionfile1);
|
|
|
|
|
// Paper start
|
|
|
|
|
@@ -814,7 +874,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
}
|
|
|
|
|
// Paper end
|
|
|
|
|
return regionfile1;
|
|
|
|
|
@@ -132,7 +149,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -132,7 +159,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -823,7 +883,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
synchronized (regionfile) {
|
|
|
|
|
try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) {
|
|
|
|
|
CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
|
|
|
|
|
@@ -179,14 +196,14 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -179,14 +206,14 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@Nullable
|
|
|
|
|
public CompoundTag read(ChunkPos pos) throws IOException {
|
|
|
|
|
// CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
|
|
|
|
|
@@ -840,7 +900,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
// We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile
|
|
|
|
|
// if we decide to re-read
|
|
|
|
|
// Paper end
|
|
|
|
|
@@ -196,7 +213,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -196,7 +223,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
|
|
|
|
|
// Paper start
|
|
|
|
|
if (regionfile.isOversized(pos.x, pos.z)) {
|
|
|
|
|
@@ -849,7 +909,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
return readOversizedChunk(regionfile, pos);
|
|
|
|
|
}
|
|
|
|
|
// Paper end
|
|
|
|
|
@@ -210,12 +227,12 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -210,12 +237,12 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
if (this.isChunkData) {
|
|
|
|
|
ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound);
|
|
|
|
|
if (!chunkPos.equals(pos)) {
|
|
|
|
|
@@ -865,7 +925,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -249,13 +266,13 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -249,13 +276,13 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
|
|
|
|
|
return nbttagcompound;
|
|
|
|
|
} finally { // Paper start
|
|
|
|
|
@@ -881,7 +941,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
if (regionfile == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@@ -285,7 +302,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -285,7 +312,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException {
|
|
|
|
|
@@ -890,7 +950,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
if (nbt == null && regionfile == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@@ -335,7 +352,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -335,7 +362,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
}
|
|
|
|
|
// Paper end
|
|
|
|
|
} finally { // Paper start
|
|
|
|
|
@@ -899,7 +959,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
} // Paper end
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -344,7 +361,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -344,7 +371,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
ObjectIterator objectiterator = this.regionCache.values().iterator();
|
|
|
|
|
|
|
|
|
|
while (objectiterator.hasNext()) {
|
|
|
|
|
@@ -908,7 +968,7 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
regionfile.close();
|
|
|
|
|
@@ -360,7 +377,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
@@ -360,7 +387,7 @@ public class RegionFileStorage implements AutoCloseable {
|
|
|
|
|
ObjectIterator objectiterator = this.regionCache.values().iterator();
|
|
|
|
|
|
|
|
|
|
while (objectiterator.hasNext()) {
|
|
|
|
|
@@ -917,8 +977,23 @@ index bd502ca721de0cab438d995efa00ad0554c0d2fe..37242c6225795271e0ba6a853252f2da
|
|
|
|
|
|
|
|
|
|
regionfile.flush();
|
|
|
|
|
}
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
|
|
|
|
index d783072bc964e45c308197e6f79874eb4a09f871..f335bb86031c4efbc88f44d542eefcc6815cd2b7 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
|
|
|
|
|
@@ -47,8 +47,8 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
|
|
|
|
|
public final RegistryAccess registryAccess; // Paper - rewrite chunk system
|
|
|
|
|
protected final LevelHeightAccessor levelHeightAccessor;
|
|
|
|
|
|
|
|
|
|
- public SectionStorage(Path path, Function<Runnable, Codec<R>> codecFactory, Function<Runnable, R> factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) {
|
|
|
|
|
- super(path, dsync); // Paper - remove mojang I/O thread
|
|
|
|
|
+ public SectionStorage(String format, int linearCompression, int linearInternalCompression, Path path, Function<Runnable, Codec<R>> codecFactory, Function<Runnable, R> factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) { // Kaiiju
|
|
|
|
|
+ super(format, linearCompression, linearInternalCompression, path, dsync); // Paper - remove mojang I/O thread // Kaiiju
|
|
|
|
|
this.codec = codecFactory;
|
|
|
|
|
this.factory = factory;
|
|
|
|
|
this.fixerUpper = dataFixer;
|
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
|
|
|
index 9b5323edd99848b73ea0e34230e7bec8dc23be8c..619c7713d573e8f438ea23a3fcb185a98abf5100 100644
|
|
|
|
|
index 6a327616cd590b70170f8441c003a2109640201d..2ef0a41fd9ee60bc0866cf5c678498c68250689c 100644
|
|
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
|
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
|
|
|
@@ -552,7 +552,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
|
|