Clean patches
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/abomination/IRegionFile.java
|
||||
@@ -1,0 +_,39 @@
|
||||
+package abomination;
|
||||
+
|
||||
+import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile;
|
||||
+import net.minecraft.nbt.CompoundTag;
|
||||
+import net.minecraft.world.level.ChunkPos;
|
||||
+
|
||||
+import java.io.DataInputStream;
|
||||
+import java.io.DataOutputStream;
|
||||
+import java.io.IOException;
|
||||
+import java.nio.ByteBuffer;
|
||||
+import java.nio.file.Path;
|
||||
+
|
||||
+public interface IRegionFile extends ChunkSystemRegionFile, AutoCloseable {
|
||||
+ Path getPath();
|
||||
+
|
||||
+ DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException;
|
||||
+
|
||||
+ boolean doesChunkExist(ChunkPos pos) throws Exception;
|
||||
+
|
||||
+ DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException;
|
||||
+
|
||||
+ void flush() throws IOException;
|
||||
+
|
||||
+ void clear(ChunkPos pos) throws IOException;
|
||||
+
|
||||
+ boolean hasChunk(ChunkPos pos);
|
||||
+
|
||||
+ void close() throws IOException;
|
||||
+
|
||||
+ void write(ChunkPos pos, ByteBuffer buf) throws IOException;
|
||||
+
|
||||
+ CompoundTag getOversizedData(int x, int z) throws IOException;
|
||||
+
|
||||
+ boolean isOversized(int x, int z);
|
||||
+
|
||||
+ boolean recalculateHeader() throws IOException;
|
||||
+
|
||||
+ void setOversized(int x, int z, boolean oversized) throws IOException;
|
||||
+}
|
||||
@@ -0,0 +1,625 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/abomination/LinearRegionFile.java
|
||||
@@ -1,0 +_,622 @@
|
||||
+package abomination;
|
||||
+
|
||||
+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
|
||||
+import com.github.luben.zstd.ZstdInputStream;
|
||||
+import com.github.luben.zstd.ZstdOutputStream;
|
||||
+import com.mojang.logging.LogUtils;
|
||||
+import net.jpountz.lz4.LZ4Compressor;
|
||||
+import net.jpountz.lz4.LZ4Factory;
|
||||
+import net.jpountz.lz4.LZ4FastDecompressor;
|
||||
+import net.openhft.hashing.LongHashFunction;
|
||||
+import net.minecraft.nbt.CompoundTag;
|
||||
+import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
+import net.minecraft.world.level.chunk.storage.RegionFileVersion;
|
||||
+import net.minecraft.world.level.ChunkPos;
|
||||
+import org.slf4j.Logger;
|
||||
+
|
||||
+import javax.annotation.Nullable;
|
||||
+import java.io.*;
|
||||
+import java.nio.ByteBuffer;
|
||||
+import java.nio.file.Files;
|
||||
+import java.nio.file.Path;
|
||||
+import java.nio.file.StandardCopyOption;
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.Arrays;
|
||||
+import java.util.List;
|
||||
+import java.util.concurrent.TimeUnit;
|
||||
+import java.util.concurrent.locks.LockSupport;
|
||||
+import java.util.concurrent.locks.ReentrantLock;
|
||||
+
|
||||
+// LinearRegionFile_implementation_version_0_5byXymb
|
||||
+// Just gonna use this string to inform other forks about updates ;-)
|
||||
+public class LinearRegionFile implements IRegionFile{
|
||||
+ private static final long SUPERBLOCK = 0xc3ff13183cca9d9aL;
|
||||
+ private static final byte VERSION = 3;
|
||||
+ private static final int HEADER_SIZE = 27;
|
||||
+ private static final int FOOTER_SIZE = 8;
|
||||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||||
+
|
||||
+ private byte[][] bucketBuffers;
|
||||
+ private final byte[][] buffer = new byte[1024][];
|
||||
+ private final int[] bufferUncompressedSize = new int[1024];
|
||||
+
|
||||
+ private final long[] chunkTimestamps = new long[1024];
|
||||
+ private final Object markedToSaveLock = new Object();
|
||||
+
|
||||
+ private final LZ4Compressor compressor;
|
||||
+ private final LZ4FastDecompressor decompressor;
|
||||
+
|
||||
+ private boolean markedToSave = false;
|
||||
+ private boolean close = false;
|
||||
+
|
||||
+ public final ReentrantLock fileLock = new ReentrantLock(true);
|
||||
+ public Path regionFile;
|
||||
+
|
||||
+ private final int compressionLevel;
|
||||
+ private int gridSize = 8;
|
||||
+ private int bucketSize = 4;
|
||||
+ private final Thread bindThread;
|
||||
+
|
||||
+ public Path getRegionFile() {
|
||||
+ return this.regionFile;
|
||||
+ }
|
||||
+
|
||||
+ public ReentrantLock getFileLock() {
|
||||
+ return this.fileLock;
|
||||
+ }
|
||||
+
|
||||
+ private int chunkToBucketIdx(int chunkX, int chunkZ) {
|
||||
+ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize;
|
||||
+ return bx * gridSize + bz;
|
||||
+ }
|
||||
+
|
||||
+ private void openBucket(int chunkX, int chunkZ) {
|
||||
+ chunkX = Math.floorMod(chunkX, 32);
|
||||
+ chunkZ = Math.floorMod(chunkZ, 32);
|
||||
+ int idx = chunkToBucketIdx(chunkX, chunkZ);
|
||||
+
|
||||
+ if (bucketBuffers == null) return;
|
||||
+ if (bucketBuffers[idx] != null) {
|
||||
+ try {
|
||||
+ ByteArrayInputStream bucketByteStream = new ByteArrayInputStream(bucketBuffers[idx]);
|
||||
+ ZstdInputStream zstdStream = new ZstdInputStream(bucketByteStream);
|
||||
+ ByteBuffer bucketBuffer = ByteBuffer.wrap(zstdStream.readAllBytes());
|
||||
+
|
||||
+ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize;
|
||||
+
|
||||
+ for (int cx = 0; cx < 32 / gridSize; cx++) {
|
||||
+ for (int cz = 0; cz < 32 / gridSize; cz++) {
|
||||
+ int chunkIndex = (bx * (32 / gridSize) + cx) + (bz * (32 / gridSize) + cz) * 32;
|
||||
+
|
||||
+ int chunkSize = bucketBuffer.getInt();
|
||||
+ long timestamp = bucketBuffer.getLong();
|
||||
+ this.chunkTimestamps[chunkIndex] = timestamp;
|
||||
+
|
||||
+ if (chunkSize > 0) {
|
||||
+ byte[] chunkData = new byte[chunkSize - 8];
|
||||
+ bucketBuffer.get(chunkData);
|
||||
+
|
||||
+ int maxCompressedLength = this.compressor.maxCompressedLength(chunkData.length);
|
||||
+ byte[] compressed = new byte[maxCompressedLength];
|
||||
+ int compressedLength = this.compressor.compress(chunkData, 0, chunkData.length, compressed, 0, maxCompressedLength);
|
||||
+ byte[] finalCompressed = new byte[compressedLength];
|
||||
+ System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength);
|
||||
+
|
||||
+ // TODO: Optimization - return the requested chunk immediately to save on one LZ4 decompression
|
||||
+ this.buffer[chunkIndex] = finalCompressed;
|
||||
+ this.bufferUncompressedSize[chunkIndex] = chunkData.length;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ } catch (IOException ex) {
|
||||
+ throw new RuntimeException("Region file corrupted: " + regionFile + " bucket: " + idx);
|
||||
+ // TODO: Make sure the server crashes instead of corrupting the world
|
||||
+ }
|
||||
+ bucketBuffers[idx] = null;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public boolean regionFileOpen = false;
|
||||
+
|
||||
+ private synchronized void openRegionFile() {
|
||||
+ if (regionFileOpen) return;
|
||||
+ regionFileOpen = true;
|
||||
+
|
||||
+ File regionFile = new File(this.regionFile.toString());
|
||||
+
|
||||
+ if(!regionFile.canRead()) {
|
||||
+ this.bindThread.start();
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ try {
|
||||
+ byte[] fileContent = Files.readAllBytes(this.regionFile);
|
||||
+ ByteBuffer buffer = ByteBuffer.wrap(fileContent);
|
||||
+
|
||||
+ long superBlock = buffer.getLong();
|
||||
+ if (superBlock != SUPERBLOCK)
|
||||
+ throw new RuntimeException("Invalid superblock: " + superBlock + " file " + this.regionFile);
|
||||
+
|
||||
+ byte version = buffer.get();
|
||||
+ if (version == 1 || version == 2) {
|
||||
+ parseLinearV1(buffer);
|
||||
+ } else if (version == 3) {
|
||||
+ parseLinearV2(buffer);
|
||||
+ } else {
|
||||
+ throw new RuntimeException("Invalid version: " + version + " file " + this.regionFile);
|
||||
+ }
|
||||
+
|
||||
+ this.bindThread.start();
|
||||
+ } catch (IOException e) {
|
||||
+ throw new RuntimeException("Failed to open region file " + this.regionFile, e);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void parseLinearV1(ByteBuffer buffer) throws IOException {
|
||||
+ final int HEADER_SIZE = 32;
|
||||
+ final int FOOTER_SIZE = 8;
|
||||
+
|
||||
+ // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused.
|
||||
+ buffer.position(buffer.position() + 11);
|
||||
+
|
||||
+ int dataCount = buffer.getInt();
|
||||
+ long fileLength = this.regionFile.toFile().length();
|
||||
+ if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) {
|
||||
+ throw new IOException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
|
||||
+ }
|
||||
+
|
||||
+ buffer.position(buffer.position() + 8); // Skip data hash (Long): Unused.
|
||||
+
|
||||
+ byte[] rawCompressed = new byte[dataCount];
|
||||
+ buffer.get(rawCompressed);
|
||||
+
|
||||
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(rawCompressed);
|
||||
+ ZstdInputStream zstdInputStream = new ZstdInputStream(byteArrayInputStream);
|
||||
+ ByteBuffer decompressedBuffer = ByteBuffer.wrap(zstdInputStream.readAllBytes());
|
||||
+
|
||||
+ int[] starts = new int[1024];
|
||||
+ for (int i = 0; i < 1024; i++) {
|
||||
+ starts[i] = decompressedBuffer.getInt();
|
||||
+ decompressedBuffer.getInt(); // Skip timestamps (Int): Unused.
|
||||
+ }
|
||||
+
|
||||
+ for (int i = 0; i < 1024; i++) {
|
||||
+ if (starts[i] > 0) {
|
||||
+ int size = starts[i];
|
||||
+ byte[] chunkData = new byte[size];
|
||||
+ decompressedBuffer.get(chunkData);
|
||||
+
|
||||
+ int maxCompressedLength = this.compressor.maxCompressedLength(size);
|
||||
+ byte[] compressed = new byte[maxCompressedLength];
|
||||
+ int compressedLength = this.compressor.compress(chunkData, 0, size, compressed, 0, maxCompressedLength);
|
||||
+ byte[] finalCompressed = new byte[compressedLength];
|
||||
+ System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength);
|
||||
+
|
||||
+ this.buffer[i] = finalCompressed;
|
||||
+ this.bufferUncompressedSize[i] = size;
|
||||
+ this.chunkTimestamps[i] = getTimestamp(); // Use current timestamp as we don't have the original
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void parseLinearV2(ByteBuffer buffer) throws IOException {
|
||||
+ buffer.getLong(); // Skip newestTimestamp (Long)
|
||||
+ gridSize = buffer.get();
|
||||
+ if (gridSize != 1 && gridSize != 2 && gridSize != 4 && gridSize != 8 && gridSize != 16 && gridSize != 32)
|
||||
+ throw new RuntimeException("Invalid grid size: " + gridSize + " file " + this.regionFile);
|
||||
+ bucketSize = 32 / gridSize;
|
||||
+
|
||||
+ buffer.getInt(); // Skip region_x (Int)
|
||||
+ buffer.getInt(); // Skip region_z (Int)
|
||||
+
|
||||
+ boolean[] chunkExistenceBitmap = deserializeExistenceBitmap(buffer);
|
||||
+
|
||||
+ while (true) {
|
||||
+ byte featureNameLength = buffer.get();
|
||||
+ if (featureNameLength == 0) break;
|
||||
+ byte[] featureNameBytes = new byte[featureNameLength];
|
||||
+ buffer.get(featureNameBytes);
|
||||
+ String featureName = new String(featureNameBytes);
|
||||
+ int featureValue = buffer.getInt();
|
||||
+ // System.out.println("NBT Feature: " + featureName + " = " + featureValue);
|
||||
+ }
|
||||
+
|
||||
+ int[] bucketSizes = new int[gridSize * gridSize];
|
||||
+ byte[] bucketCompressionLevels = new byte[gridSize * gridSize];
|
||||
+ long[] bucketHashes = new long[gridSize * gridSize];
|
||||
+ for (int i = 0; i < gridSize * gridSize; i++) {
|
||||
+ bucketSizes[i] = buffer.getInt();
|
||||
+ bucketCompressionLevels[i] = buffer.get();
|
||||
+ bucketHashes[i] = buffer.getLong();
|
||||
+ }
|
||||
+
|
||||
+ bucketBuffers = new byte[gridSize * gridSize][];
|
||||
+ for (int i = 0; i < gridSize * gridSize; i++) {
|
||||
+ if (bucketSizes[i] > 0) {
|
||||
+ bucketBuffers[i] = new byte[bucketSizes[i]];
|
||||
+ buffer.get(bucketBuffers[i]);
|
||||
+ long rawHash = LongHashFunction.xx().hashBytes(bucketBuffers[i]);
|
||||
+ if (rawHash != bucketHashes[i]) throw new IOException("Region file hash incorrect " + this.regionFile);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ long footerSuperBlock = buffer.getLong();
|
||||
+ if (footerSuperBlock != SUPERBLOCK)
|
||||
+ throw new IOException("Footer superblock invalid " + this.regionFile);
|
||||
+ }
|
||||
+
|
||||
+ public LinearRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, int compressionLevel) throws IOException {
|
||||
+ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, compressionLevel);
|
||||
+ }
|
||||
+
|
||||
+ public LinearRegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, int compressionLevel) throws IOException {
|
||||
+ Runnable flushCheck = () -> {
|
||||
+ while (!close) {
|
||||
+ synchronized (saveLock) {
|
||||
+ if (markedToSave && activeSaveThreads < SAVE_THREAD_MAX_COUNT) {
|
||||
+ activeSaveThreads++;
|
||||
+ Runnable flushOperation = () -> {
|
||||
+ try {
|
||||
+ flush();
|
||||
+ } catch (IOException ex) {
|
||||
+ LOGGER.error("Region file {} flush failed", this.regionFile.toAbsolutePath(), ex);
|
||||
+ } finally {
|
||||
+ synchronized (saveLock) {
|
||||
+ activeSaveThreads--;
|
||||
+ }
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ Thread saveThread = USE_VIRTUAL_THREAD ?
|
||||
+ Thread.ofVirtual().name("Linear IO - " + LinearRegionFile.this.hashCode()).unstarted(flushOperation) :
|
||||
+ Thread.ofPlatform().name("Linear IO - " + LinearRegionFile.this.hashCode()).unstarted(flushOperation);
|
||||
+ saveThread.setPriority(Thread.NORM_PRIORITY - 3);
|
||||
+ saveThread.start();
|
||||
+ }
|
||||
+ }
|
||||
+ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(SAVE_DELAY_MS));
|
||||
+ }
|
||||
+ };
|
||||
+ this.bindThread = USE_VIRTUAL_THREAD ? Thread.ofVirtual().unstarted(flushCheck) : Thread.ofPlatform().unstarted(flushCheck);
|
||||
+ this.bindThread.setName("Linear IO Schedule - " + this.hashCode());
|
||||
+ this.regionFile = path;
|
||||
+ this.compressionLevel = compressionLevel;
|
||||
+
|
||||
+ this.compressor = LZ4Factory.fastestInstance().fastCompressor();
|
||||
+ this.decompressor = LZ4Factory.fastestInstance().fastDecompressor();
|
||||
+ }
|
||||
+
|
||||
+ private synchronized void markToSave() {
|
||||
+ synchronized(markedToSaveLock) {
|
||||
+ markedToSave = true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private synchronized boolean isMarkedToSave() {
|
||||
+ synchronized(markedToSaveLock) {
|
||||
+ if(markedToSave) {
|
||||
+ markedToSave = false;
|
||||
+ return true;
|
||||
+ }
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static int SAVE_THREAD_MAX_COUNT = 6;
|
||||
+ public static int SAVE_DELAY_MS = 100;
|
||||
+ public static boolean USE_VIRTUAL_THREAD = true;
|
||||
+ private static final Object saveLock = new Object();
|
||||
+ private static int activeSaveThreads = 0;
|
||||
+
|
||||
+ /*public void run() {
|
||||
+ while (!close) {
|
||||
+ synchronized (saveLock) {
|
||||
+ if (markedToSave && activeSaveThreads < SAVE_THREAD_MAX_COUNT) {
|
||||
+ activeSaveThreads++;
|
||||
+ Thread saveThread = new Thread(() -> {
|
||||
+ try {
|
||||
+ flush();
|
||||
+ } catch (IOException ex) {
|
||||
+ LOGGER.error("Region file " + this.regionFile.toAbsolutePath() + " flush failed", ex);
|
||||
+ } finally {
|
||||
+ synchronized (saveLock) {
|
||||
+ activeSaveThreads--;
|
||||
+ }
|
||||
+ }
|
||||
+ }, "RegionFileFlush");
|
||||
+ saveThread.setPriority(Thread.NORM_PRIORITY - 3);
|
||||
+ saveThread.start();
|
||||
+ }
|
||||
+ }
|
||||
+ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(SAVE_DELAY_MS));
|
||||
+ }
|
||||
+ }*/
|
||||
+
|
||||
+ public synchronized boolean doesChunkExist(ChunkPos pos) throws Exception {
|
||||
+ openRegionFile();
|
||||
+ throw new Exception("doesChunkExist is a stub");
|
||||
+ }
|
||||
+
|
||||
+ public synchronized void flush() throws IOException {
|
||||
+ if(!isMarkedToSave()) return;
|
||||
+
|
||||
+ openRegionFile();
|
||||
+
|
||||
+ long timestamp = getTimestamp();
|
||||
+
|
||||
+long writeStart = System.nanoTime();
|
||||
+ File tempFile = new File(regionFile.toString() + ".tmp");
|
||||
+ FileOutputStream fileStream = new FileOutputStream(tempFile);
|
||||
+ DataOutputStream dataStream = new DataOutputStream(fileStream);
|
||||
+
|
||||
+ dataStream.writeLong(SUPERBLOCK);
|
||||
+ dataStream.writeByte(VERSION);
|
||||
+ dataStream.writeLong(timestamp);
|
||||
+ dataStream.writeByte(gridSize);
|
||||
+
|
||||
+ String fileName = regionFile.getFileName().toString();
|
||||
+ String[] parts = fileName.split("\\.");
|
||||
+ int regionX = 0;
|
||||
+ int regionZ = 0;
|
||||
+ try {
|
||||
+ if (parts.length >= 4) {
|
||||
+ regionX = Integer.parseInt(parts[1]);
|
||||
+ regionZ = Integer.parseInt(parts[2]);
|
||||
+ } else {
|
||||
+ LOGGER.warn("Unexpected file name format: " + fileName);
|
||||
+ }
|
||||
+ } catch (NumberFormatException e) {
|
||||
+ LOGGER.error("Failed to parse region coordinates from file name: " + fileName, e);
|
||||
+ }
|
||||
+
|
||||
+ dataStream.writeInt(regionX);
|
||||
+ dataStream.writeInt(regionZ);
|
||||
+
|
||||
+ boolean[] chunkExistenceBitmap = new boolean[1024];
|
||||
+ for (int i = 0; i < 1024; i++) {
|
||||
+ chunkExistenceBitmap[i] = (this.bufferUncompressedSize[i] > 0);
|
||||
+ }
|
||||
+ writeSerializedExistenceBitmap(dataStream, chunkExistenceBitmap);
|
||||
+
|
||||
+ writeNBTFeatures(dataStream);
|
||||
+
|
||||
+ int bucketMisses = 0;
|
||||
+ byte[][] buckets = new byte[gridSize * gridSize][];
|
||||
+ for (int bx = 0; bx < gridSize; bx++) {
|
||||
+ for (int bz = 0; bz < gridSize; bz++) {
|
||||
+ if (bucketBuffers != null && bucketBuffers[bx * gridSize + bz] != null) {
|
||||
+ buckets[bx * gridSize + bz] = bucketBuffers[bx * gridSize + bz];
|
||||
+ continue;
|
||||
+ }
|
||||
+ bucketMisses++;
|
||||
+
|
||||
+ ByteArrayOutputStream bucketStream = new ByteArrayOutputStream();
|
||||
+ ZstdOutputStream zstdStream = new ZstdOutputStream(bucketStream, this.compressionLevel);
|
||||
+ DataOutputStream bucketDataStream = new DataOutputStream(zstdStream);
|
||||
+
|
||||
+ boolean hasData = false;
|
||||
+ for (int cx = 0; cx < 32 / gridSize; cx++) {
|
||||
+ for (int cz = 0; cz < 32 / gridSize; cz++) {
|
||||
+ int chunkIndex = (bx * 32 / gridSize + cx) + (bz * 32 / gridSize + cz) * 32;
|
||||
+ if (this.bufferUncompressedSize[chunkIndex] > 0) {
|
||||
+ hasData = true;
|
||||
+ byte[] chunkData = new byte[this.bufferUncompressedSize[chunkIndex]];
|
||||
+ this.decompressor.decompress(this.buffer[chunkIndex], 0, chunkData, 0, this.bufferUncompressedSize[chunkIndex]);
|
||||
+ bucketDataStream.writeInt(chunkData.length + 8);
|
||||
+ bucketDataStream.writeLong(this.chunkTimestamps[chunkIndex]);
|
||||
+ bucketDataStream.write(chunkData);
|
||||
+ } else {
|
||||
+ bucketDataStream.writeInt(0);
|
||||
+ bucketDataStream.writeLong(this.chunkTimestamps[chunkIndex]);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ bucketDataStream.close();
|
||||
+
|
||||
+ if (hasData) {
|
||||
+ buckets[bx * gridSize + bz] = bucketStream.toByteArray();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (int i = 0; i < gridSize * gridSize; i++) {
|
||||
+ dataStream.writeInt(buckets[i] != null ? buckets[i].length : 0);
|
||||
+ dataStream.writeByte(this.compressionLevel);
|
||||
+ long rawHash = 0;
|
||||
+ if (buckets[i] != null) {
|
||||
+ rawHash = LongHashFunction.xx().hashBytes(buckets[i]);
|
||||
+ }
|
||||
+ dataStream.writeLong(rawHash);
|
||||
+ }
|
||||
+
|
||||
+ for (int i = 0; i < gridSize * gridSize; i++) {
|
||||
+ if (buckets[i] != null) {
|
||||
+ dataStream.write(buckets[i]);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ dataStream.writeLong(SUPERBLOCK);
|
||||
+
|
||||
+ dataStream.flush();
|
||||
+ fileStream.getFD().sync();
|
||||
+ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs
|
||||
+ dataStream.close();
|
||||
+
|
||||
+ fileStream.close();
|
||||
+ Files.move(tempFile.toPath(), this.regionFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
+//System.out.println("writeStart REGION FILE FLUSH " + (System.nanoTime() - writeStart) + " misses: " + bucketMisses);
|
||||
+ }
|
||||
+
|
||||
+ private void writeNBTFeatures(DataOutputStream dataStream) throws IOException {
|
||||
+ // writeNBTFeature(dataStream, "example", 1);
|
||||
+ dataStream.writeByte(0); // End of NBT features
|
||||
+ }
|
||||
+
|
||||
+ private void writeNBTFeature(DataOutputStream dataStream, String featureName, int featureValue) throws IOException {
|
||||
+ byte[] featureNameBytes = featureName.getBytes();
|
||||
+ dataStream.writeByte(featureNameBytes.length);
|
||||
+ dataStream.write(featureNameBytes);
|
||||
+ dataStream.writeInt(featureValue);
|
||||
+ }
|
||||
+
|
||||
+ public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Abomination - prevent chunk dupe
|
||||
+
|
||||
+ public synchronized void write(ChunkPos pos, ByteBuffer buffer) {
|
||||
+ openRegionFile();
|
||||
+ openBucket(pos.x, pos.z);
|
||||
+ try {
|
||||
+ byte[] b = toByteArray(new ByteArrayInputStream(buffer.array()));
|
||||
+ int uncompressedSize = b.length;
|
||||
+
|
||||
+ if (uncompressedSize > MAX_CHUNK_SIZE) {
|
||||
+ LOGGER.error("Chunk dupe attempt " + this.regionFile);
|
||||
+ clear(pos);
|
||||
+ } else {
|
||||
+ int maxCompressedLength = this.compressor.maxCompressedLength(b.length);
|
||||
+ byte[] compressed = new byte[maxCompressedLength];
|
||||
+ int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength);
|
||||
+ b = new byte[compressedLength];
|
||||
+ System.arraycopy(compressed, 0, b, 0, compressedLength);
|
||||
+
|
||||
+ int index = getChunkIndex(pos.x, pos.z);
|
||||
+ this.buffer[index] = b;
|
||||
+ this.chunkTimestamps[index] = getTimestamp();
|
||||
+ this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize;
|
||||
+ }
|
||||
+ } catch (IOException e) {
|
||||
+ LOGGER.error("Chunk write IOException " + e + " " + this.regionFile);
|
||||
+ }
|
||||
+ markToSave();
|
||||
+ }
|
||||
+
|
||||
+ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) {
|
||||
+ openRegionFile();
|
||||
+ openBucket(pos.x, pos.z);
|
||||
+ return new DataOutputStream(new BufferedOutputStream(new LinearRegionFile.ChunkBuffer(pos)));
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) throws IOException {
|
||||
+ final DataOutputStream out = this.getChunkDataOutputStream(pos);
|
||||
+
|
||||
+ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData(
|
||||
+ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE,
|
||||
+ out, regionFile -> out.close()
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ private class ChunkBuffer extends ByteArrayOutputStream {
|
||||
+
|
||||
+ private final ChunkPos pos;
|
||||
+
|
||||
+ public ChunkBuffer(ChunkPos chunkcoordintpair) {
|
||||
+ super();
|
||||
+ this.pos = chunkcoordintpair;
|
||||
+ }
|
||||
+
|
||||
+ public void close() throws IOException {
|
||||
+ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count);
|
||||
+ LinearRegionFile.this.write(this.pos, bytebuffer);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private byte[] toByteArray(InputStream in) throws IOException {
|
||||
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
+ byte[] tempBuffer = new byte[4096];
|
||||
+
|
||||
+ int length;
|
||||
+ while ((length = in.read(tempBuffer)) >= 0) {
|
||||
+ out.write(tempBuffer, 0, length);
|
||||
+ }
|
||||
+
|
||||
+ return out.toByteArray();
|
||||
+ }
|
||||
+
|
||||
+ @Nullable
|
||||
+ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) {
|
||||
+ openRegionFile();
|
||||
+ openBucket(pos.x, pos.z);
|
||||
+
|
||||
+ if(this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) {
|
||||
+ byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]];
|
||||
+ this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]);
|
||||
+ return new DataInputStream(new ByteArrayInputStream(content));
|
||||
+ }
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ public synchronized void clear(ChunkPos pos) {
|
||||
+ openRegionFile();
|
||||
+ openBucket(pos.x, pos.z);
|
||||
+ int i = getChunkIndex(pos.x, pos.z);
|
||||
+ this.buffer[i] = null;
|
||||
+ this.bufferUncompressedSize[i] = 0;
|
||||
+ this.chunkTimestamps[i] = 0;
|
||||
+ markToSave();
|
||||
+ }
|
||||
+
|
||||
+ public synchronized boolean hasChunk(ChunkPos pos) {
|
||||
+ openRegionFile();
|
||||
+ openBucket(pos.x, pos.z);
|
||||
+ return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0;
|
||||
+ }
|
||||
+
|
||||
+ public synchronized void close() throws IOException {
|
||||
+ openRegionFile();
|
||||
+ close = true;
|
||||
+ try {
|
||||
+ flush();
|
||||
+ } catch(IOException e) {
|
||||
+ throw new IOException("Region flush IOException " + e + " " + this.regionFile);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static int getChunkIndex(int x, int z) {
|
||||
+ return (x & 31) + ((z & 31) << 5);
|
||||
+ }
|
||||
+
|
||||
+ private static int getTimestamp() {
|
||||
+ return (int) (System.currentTimeMillis() / 1000L);
|
||||
+ }
|
||||
+
|
||||
+ public boolean recalculateHeader() {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ public void setOversized(int x, int z, boolean something) {}
|
||||
+
|
||||
+ public CompoundTag getOversizedData(int x, int z) throws IOException {
|
||||
+ throw new IOException("getOversizedData is a stub " + this.regionFile);
|
||||
+ }
|
||||
+
|
||||
+ public boolean isOversized(int x, int z) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ public Path getPath() {
|
||||
+ return this.regionFile;
|
||||
+ }
|
||||
+
|
||||
+ private boolean[] deserializeExistenceBitmap(ByteBuffer buffer) {
|
||||
+ boolean[] result = new boolean[1024];
|
||||
+ for (int i = 0; i < 128; i++) {
|
||||
+ byte b = buffer.get();
|
||||
+ for (int j = 0; j < 8; j++) {
|
||||
+ result[i * 8 + j] = ((b >> (7 - j)) & 1) == 1;
|
||||
+ }
|
||||
+ }
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ private void writeSerializedExistenceBitmap(DataOutputStream out, boolean[] bitmap) throws IOException {
|
||||
+ for (int i = 0; i < 128; i++) {
|
||||
+ byte b = 0;
|
||||
+ for (int j = 0; j < 8; j++) {
|
||||
+ if (bitmap[i * 8 + j]) {
|
||||
+ b |= (1 << (7 - j));
|
||||
+ }
|
||||
+ }
|
||||
+ out.writeByte(b);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,93 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/kiocg/ChunkHot.java
|
||||
@@ -1,0 +_,90 @@
|
||||
+package com.kiocg;
|
||||
+
|
||||
+import java.util.Arrays;
|
||||
+
|
||||
+public class ChunkHot {
|
||||
+ // 热度统计总区间数量
|
||||
+ private static final int TIMES_LENGTH = 10;
|
||||
+ // 当前统计区间下标
|
||||
+ private int index = -1;
|
||||
+
|
||||
+ // 热度统计区间
|
||||
+ private final long[] times = new long[TIMES_LENGTH];
|
||||
+ // 存放临时的区间数值
|
||||
+ // 用于修正正在统计的当前区间热度没有计入总值的问题
|
||||
+ private long temp;
|
||||
+ // 所有区间的热度总值
|
||||
+ private long total;
|
||||
+
|
||||
+ // 用于每个具体统计的计算
|
||||
+ private long nanos;
|
||||
+ // 当前统计是否进行中
|
||||
+ private volatile boolean started = false;
|
||||
+
|
||||
+ /**
|
||||
+ * 更新区间下标
|
||||
+ */
|
||||
+ public void nextTick() {
|
||||
+ this.index = ++this.index % TIMES_LENGTH;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * 开始统计一个新区间
|
||||
+ */
|
||||
+ public void start() {
|
||||
+ started = true;
|
||||
+ temp = times[this.index];
|
||||
+ times[this.index] = 0L;
|
||||
+ }
|
||||
+
|
||||
+ public boolean isStarted(){
|
||||
+ return this.started;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * 结束当前区间的统计
|
||||
+ * 将统计值更新入热度总值
|
||||
+ */
|
||||
+ public void stop() {
|
||||
+ started = false;
|
||||
+ total -= temp;
|
||||
+ total += times[this.index];
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * 开始一个具体统计
|
||||
+ */
|
||||
+ public void startTicking() {
|
||||
+ if (!started) return;
|
||||
+ nanos = System.nanoTime();
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * 结束一个具体统计
|
||||
+ * 将统计值计入当前热度区间
|
||||
+ */
|
||||
+ public void stopTickingAndCount() {
|
||||
+ if (!started) return;
|
||||
+ // 定义一个具体统计的最大值为 1,000,000
|
||||
+ // 有时候某个具体统计的计算值会在某1刻飙升,可能是由于保存数据到磁盘?
|
||||
+ times[this.index] += Math.min(System.nanoTime() - nanos, 1000000L);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * 清空统计 (当区块卸载时)
|
||||
+ */
|
||||
+ public void clear() {
|
||||
+ started = false;
|
||||
+ Arrays.fill(times, 0L);
|
||||
+ temp = 0L;
|
||||
+ total = 0L;
|
||||
+ nanos = 0L;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * @return 获取区块热度平均值
|
||||
+ */
|
||||
+ public long getAverage() {
|
||||
+ return total / ((long) TIMES_LENGTH * 20L);
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,37 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/logisticscraft/occlusionculling/DataProvider.java
|
||||
@@ -1,0 +_,34 @@
|
||||
+package com.logisticscraft.occlusionculling;
|
||||
+
|
||||
+import com.logisticscraft.occlusionculling.util.Vec3d;
|
||||
+
|
||||
+public interface DataProvider {
|
||||
+
|
||||
+ /**
|
||||
+ * Prepares the requested chunk. Returns true if the chunk is ready, false when
|
||||
+ * not loaded. Should not reload the chunk when the x and y are the same as the
|
||||
+ * last request!
|
||||
+ *
|
||||
+ * @param chunkX
|
||||
+ * @param chunkZ
|
||||
+ * @return
|
||||
+ */
|
||||
+ boolean prepareChunk(int chunkX, int chunkZ);
|
||||
+
|
||||
+ /**
|
||||
+ * Location is inside the chunk.
|
||||
+ *
|
||||
+ * @param x
|
||||
+ * @param y
|
||||
+ * @param z
|
||||
+ * @return
|
||||
+ */
|
||||
+ boolean isOpaqueFullCube(int x, int y, int z);
|
||||
+
|
||||
+ default void cleanup() {
|
||||
+ }
|
||||
+
|
||||
+ default void checkingPosition(Vec3d[] targetPoints, int size, Vec3d viewerPosition) {
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,518 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/logisticscraft/occlusionculling/OcclusionCullingInstance.java
|
||||
@@ -1,0 +_,515 @@
|
||||
+package com.logisticscraft.occlusionculling;
|
||||
+
|
||||
+import java.util.Arrays;
|
||||
+import java.util.BitSet;
|
||||
+
|
||||
+import com.logisticscraft.occlusionculling.cache.ArrayOcclusionCache;
|
||||
+import com.logisticscraft.occlusionculling.cache.OcclusionCache;
|
||||
+import com.logisticscraft.occlusionculling.util.MathUtilities;
|
||||
+import com.logisticscraft.occlusionculling.util.Vec3d;
|
||||
+
|
||||
+public class OcclusionCullingInstance {
|
||||
+
|
||||
+ private static final int ON_MIN_X = 0x01;
|
||||
+ private static final int ON_MAX_X = 0x02;
|
||||
+ private static final int ON_MIN_Y = 0x04;
|
||||
+ private static final int ON_MAX_Y = 0x08;
|
||||
+ private static final int ON_MIN_Z = 0x10;
|
||||
+ private static final int ON_MAX_Z = 0x20;
|
||||
+
|
||||
+ private final int reach;
|
||||
+ private final double aabbExpansion;
|
||||
+ private final DataProvider provider;
|
||||
+ private final OcclusionCache cache;
|
||||
+
|
||||
+ // Reused allocated data structures
|
||||
+ private final BitSet skipList = new BitSet(); // Grows bigger in case some mod introduces giant hitboxes
|
||||
+ private final Vec3d[] targetPoints = new Vec3d[15];
|
||||
+ private final Vec3d targetPos = new Vec3d(0, 0, 0);
|
||||
+ private final int[] cameraPos = new int[3];
|
||||
+ private final boolean[] dotselectors = new boolean[14];
|
||||
+ private boolean allowRayChecks = false;
|
||||
+ private final int[] lastHitBlock = new int[3];
|
||||
+ private boolean allowWallClipping = false;
|
||||
+
|
||||
+
|
||||
+ public OcclusionCullingInstance(int maxDistance, DataProvider provider) {
|
||||
+ this(maxDistance, provider, new ArrayOcclusionCache(maxDistance), 0.5);
|
||||
+ }
|
||||
+
|
||||
+ public OcclusionCullingInstance(int maxDistance, DataProvider provider, OcclusionCache cache, double aabbExpansion) {
|
||||
+ this.reach = maxDistance;
|
||||
+ this.provider = provider;
|
||||
+ this.cache = cache;
|
||||
+ this.aabbExpansion = aabbExpansion;
|
||||
+ for(int i = 0; i < targetPoints.length; i++) {
|
||||
+ targetPoints[i] = new Vec3d(0, 0, 0);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public boolean isAABBVisible(Vec3d aabbMin, Vec3d aabbMax, Vec3d viewerPosition) {
|
||||
+ try {
|
||||
+ int maxX = MathUtilities.floor(aabbMax.x
|
||||
+ + aabbExpansion);
|
||||
+ int maxY = MathUtilities.floor(aabbMax.y
|
||||
+ + aabbExpansion);
|
||||
+ int maxZ = MathUtilities.floor(aabbMax.z
|
||||
+ + aabbExpansion);
|
||||
+ int minX = MathUtilities.floor(aabbMin.x
|
||||
+ - aabbExpansion);
|
||||
+ int minY = MathUtilities.floor(aabbMin.y
|
||||
+ - aabbExpansion);
|
||||
+ int minZ = MathUtilities.floor(aabbMin.z
|
||||
+ - aabbExpansion);
|
||||
+
|
||||
+ cameraPos[0] = MathUtilities.floor(viewerPosition.x);
|
||||
+ cameraPos[1] = MathUtilities.floor(viewerPosition.y);
|
||||
+ cameraPos[2] = MathUtilities.floor(viewerPosition.z);
|
||||
+
|
||||
+ Relative relX = Relative.from(minX, maxX, cameraPos[0]);
|
||||
+ Relative relY = Relative.from(minY, maxY, cameraPos[1]);
|
||||
+ Relative relZ = Relative.from(minZ, maxZ, cameraPos[2]);
|
||||
+
|
||||
+ if(relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) {
|
||||
+ return true; // We are inside of the AABB, don't cull
|
||||
+ }
|
||||
+
|
||||
+ skipList.clear();
|
||||
+
|
||||
+ // Just check the cache first
|
||||
+ int id = 0;
|
||||
+ for (int x = minX; x <= maxX; x++) {
|
||||
+ for (int y = minY; y <= maxY; y++) {
|
||||
+ for (int z = minZ; z <= maxZ; z++) {
|
||||
+ int cachedValue = getCacheValue(x, y, z);
|
||||
+
|
||||
+ if (cachedValue == 1) {
|
||||
+ // non-occluding
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (cachedValue != 0) {
|
||||
+ // was checked and it wasn't visible
|
||||
+ skipList.set(id);
|
||||
+ }
|
||||
+ id++;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // only after the first hit wall the cache becomes valid.
|
||||
+ allowRayChecks = false;
|
||||
+
|
||||
+ // since the cache wasn't helpfull
|
||||
+ id = 0;
|
||||
+ for (int x = minX; x <= maxX; x++) {
|
||||
+ byte visibleOnFaceX = 0;
|
||||
+ byte faceEdgeDataX = 0;
|
||||
+ faceEdgeDataX |= (x == minX) ? ON_MIN_X : 0;
|
||||
+ faceEdgeDataX |= (x == maxX) ? ON_MAX_X : 0;
|
||||
+ visibleOnFaceX |= (x == minX && relX == Relative.POSITIVE) ? ON_MIN_X : 0;
|
||||
+ visibleOnFaceX |= (x == maxX && relX == Relative.NEGATIVE) ? ON_MAX_X : 0;
|
||||
+ for (int y = minY; y <= maxY; y++) {
|
||||
+ byte faceEdgeDataY = faceEdgeDataX;
|
||||
+ byte visibleOnFaceY = visibleOnFaceX;
|
||||
+ faceEdgeDataY |= (y == minY) ? ON_MIN_Y : 0;
|
||||
+ faceEdgeDataY |= (y == maxY) ? ON_MAX_Y : 0;
|
||||
+ visibleOnFaceY |= (y == minY && relY == Relative.POSITIVE) ? ON_MIN_Y : 0;
|
||||
+ visibleOnFaceY |= (y == maxY && relY == Relative.NEGATIVE) ? ON_MAX_Y : 0;
|
||||
+ for (int z = minZ; z <= maxZ; z++) {
|
||||
+ byte faceEdgeData = faceEdgeDataY;
|
||||
+ byte visibleOnFace = visibleOnFaceY;
|
||||
+ faceEdgeData |= (z == minZ) ? ON_MIN_Z : 0;
|
||||
+ faceEdgeData |= (z == maxZ) ? ON_MAX_Z : 0;
|
||||
+ visibleOnFace |= (z == minZ && relZ == Relative.POSITIVE) ? ON_MIN_Z : 0;
|
||||
+ visibleOnFace |= (z == maxZ && relZ == Relative.NEGATIVE) ? ON_MAX_Z : 0;
|
||||
+ if(skipList.get(id)) { // was checked and it wasn't visible
|
||||
+ id++;
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (visibleOnFace != 0) {
|
||||
+ targetPos.set(x, y, z);
|
||||
+ if (isVoxelVisible(viewerPosition, targetPos, faceEdgeData, visibleOnFace)) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ id++;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+ } catch (Throwable t) {
|
||||
+ // Failsafe
|
||||
+ t.printStackTrace();
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * @param viewerPosition
|
||||
+ * @param position
|
||||
+ * @param faceData contains rather this Block is on the outside for a given face
|
||||
+ * @param visibleOnFace contains rather a face should be concidered
|
||||
+ * @return
|
||||
+ */
|
||||
+ private boolean isVoxelVisible(Vec3d viewerPosition, Vec3d position, byte faceData, byte visibleOnFace) {
|
||||
+ int targetSize = 0;
|
||||
+ Arrays.fill(dotselectors, false);
|
||||
+ if((visibleOnFace & ON_MIN_X) == ON_MIN_X){
|
||||
+ dotselectors[0] = true;
|
||||
+ if((faceData & ~ON_MIN_X) != 0) {
|
||||
+ dotselectors[1] = true;
|
||||
+ dotselectors[4] = true;
|
||||
+ dotselectors[5] = true;
|
||||
+ }
|
||||
+ dotselectors[8] = true;
|
||||
+ }
|
||||
+ if((visibleOnFace & ON_MIN_Y) == ON_MIN_Y){
|
||||
+ dotselectors[0] = true;
|
||||
+ if((faceData & ~ON_MIN_Y) != 0) {
|
||||
+ dotselectors[3] = true;
|
||||
+ dotselectors[4] = true;
|
||||
+ dotselectors[7] = true;
|
||||
+ }
|
||||
+ dotselectors[9] = true;
|
||||
+ }
|
||||
+ if((visibleOnFace & ON_MIN_Z) == ON_MIN_Z){
|
||||
+ dotselectors[0] = true;
|
||||
+ if((faceData & ~ON_MIN_Z) != 0) {
|
||||
+ dotselectors[1] = true;
|
||||
+ dotselectors[4] = true;
|
||||
+ dotselectors[5] = true;
|
||||
+ }
|
||||
+ dotselectors[10] = true;
|
||||
+ }
|
||||
+ if((visibleOnFace & ON_MAX_X) == ON_MAX_X){
|
||||
+ dotselectors[4] = true;
|
||||
+ if((faceData & ~ON_MAX_X) != 0) {
|
||||
+ dotselectors[5] = true;
|
||||
+ dotselectors[6] = true;
|
||||
+ dotselectors[7] = true;
|
||||
+ }
|
||||
+ dotselectors[11] = true;
|
||||
+ }
|
||||
+ if((visibleOnFace & ON_MAX_Y) == ON_MAX_Y){
|
||||
+ dotselectors[1] = true;
|
||||
+ if((faceData & ~ON_MAX_Y) != 0) {
|
||||
+ dotselectors[2] = true;
|
||||
+ dotselectors[5] = true;
|
||||
+ dotselectors[6] = true;
|
||||
+ }
|
||||
+ dotselectors[12] = true;
|
||||
+ }
|
||||
+ if((visibleOnFace & ON_MAX_Z) == ON_MAX_Z){
|
||||
+ dotselectors[2] = true;
|
||||
+ if((faceData & ~ON_MAX_Z) != 0) {
|
||||
+ dotselectors[3] = true;
|
||||
+ dotselectors[6] = true;
|
||||
+ dotselectors[7] = true;
|
||||
+ }
|
||||
+ dotselectors[13] = true;
|
||||
+ }
|
||||
+
|
||||
+ if (dotselectors[0])targetPoints[targetSize++].setAdd(position, 0.05, 0.05, 0.05);
|
||||
+ if (dotselectors[1])targetPoints[targetSize++].setAdd(position, 0.05, 0.95, 0.05);
|
||||
+ if (dotselectors[2])targetPoints[targetSize++].setAdd(position, 0.05, 0.95, 0.95);
|
||||
+ if (dotselectors[3])targetPoints[targetSize++].setAdd(position, 0.05, 0.05, 0.95);
|
||||
+ if (dotselectors[4])targetPoints[targetSize++].setAdd(position, 0.95, 0.05, 0.05);
|
||||
+ if (dotselectors[5])targetPoints[targetSize++].setAdd(position, 0.95, 0.95, 0.05);
|
||||
+ if (dotselectors[6])targetPoints[targetSize++].setAdd(position, 0.95, 0.95, 0.95);
|
||||
+ if (dotselectors[7])targetPoints[targetSize++].setAdd(position, 0.95, 0.05, 0.95);
|
||||
+ // middle points
|
||||
+ if (dotselectors[8])targetPoints[targetSize++].setAdd(position, 0.05, 0.5, 0.5);
|
||||
+ if (dotselectors[9])targetPoints[targetSize++].setAdd(position, 0.5, 0.05, 0.5);
|
||||
+ if (dotselectors[10])targetPoints[targetSize++].setAdd(position, 0.5, 0.5, 0.05);
|
||||
+ if (dotselectors[11])targetPoints[targetSize++].setAdd(position, 0.95, 0.5, 0.5);
|
||||
+ if (dotselectors[12])targetPoints[targetSize++].setAdd(position, 0.5, 0.95, 0.5);
|
||||
+ if (dotselectors[13])targetPoints[targetSize++].setAdd(position, 0.5, 0.5, 0.95);
|
||||
+
|
||||
+ return isVisible(viewerPosition, targetPoints, targetSize);
|
||||
+ }
|
||||
+
|
||||
+ private boolean rayIntersection(int[] b, Vec3d rayOrigin, Vec3d rayDir) {
|
||||
+ Vec3d rInv = new Vec3d(1, 1, 1).div(rayDir);
|
||||
+
|
||||
+ double t1 = (b[0] - rayOrigin.x) * rInv.x;
|
||||
+ double t2 = (b[0] + 1 - rayOrigin.x) * rInv.x;
|
||||
+ double t3 = (b[1] - rayOrigin.y) * rInv.y;
|
||||
+ double t4 = (b[1] + 1 - rayOrigin.y) * rInv.y;
|
||||
+ double t5 = (b[2] - rayOrigin.z) * rInv.z;
|
||||
+ double t6 = (b[2] + 1 - rayOrigin.z) * rInv.z;
|
||||
+
|
||||
+ double tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6));
|
||||
+ double tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6));
|
||||
+
|
||||
+ // if tmax > 0, ray (line) is intersecting AABB, but the whole AABB is behind us
|
||||
+ if (tmax > 0) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ // if tmin > tmax, ray doesn't intersect AABB
|
||||
+ if (tmin > tmax) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * returns the grid cells that intersect with this Vec3d<br>
|
||||
+ * <a href=
|
||||
+ * "http://playtechs.blogspot.de/2007/03/raytracing-on-grid.html">http://playtechs.blogspot.de/2007/03/raytracing-on-grid.html</a>
|
||||
+ * <p>
|
||||
+ * Caching assumes that all Vec3d's are inside the same block
|
||||
+ */
|
||||
+ private boolean isVisible(Vec3d start, Vec3d[] targets, int size) {
|
||||
+ // start cell coordinate
|
||||
+ int x = cameraPos[0];
|
||||
+ int y = cameraPos[1];
|
||||
+ int z = cameraPos[2];
|
||||
+
|
||||
+ for (int v = 0; v < size; v++) {
|
||||
+ // ray-casting target
|
||||
+ Vec3d target = targets[v];
|
||||
+
|
||||
+ double relativeX = start.x - target.getX();
|
||||
+ double relativeY = start.y - target.getY();
|
||||
+ double relativeZ = start.z - target.getZ();
|
||||
+
|
||||
+ if(allowRayChecks && rayIntersection(lastHitBlock, start, new Vec3d(relativeX, relativeY, relativeZ).normalize())) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ // horizontal and vertical cell amount spanned
|
||||
+ double dimensionX = Math.abs(relativeX);
|
||||
+ double dimensionY = Math.abs(relativeY);
|
||||
+ double dimensionZ = Math.abs(relativeZ);
|
||||
+
|
||||
+ // distance between horizontal intersection points with cell border as a
|
||||
+ // fraction of the total Vec3d length
|
||||
+ double dimFracX = 1f / dimensionX;
|
||||
+ // distance between vertical intersection points with cell border as a fraction
|
||||
+ // of the total Vec3d length
|
||||
+ double dimFracY = 1f / dimensionY;
|
||||
+ double dimFracZ = 1f / dimensionZ;
|
||||
+
|
||||
+ // total amount of intersected cells
|
||||
+ int intersectCount = 1;
|
||||
+
|
||||
+ // 1, 0 or -1
|
||||
+ // determines the direction of the next cell (horizontally / vertically)
|
||||
+ int x_inc, y_inc, z_inc;
|
||||
+
|
||||
+ // the distance to the next horizontal / vertical intersection point with a cell
|
||||
+ // border as a fraction of the total Vec3d length
|
||||
+ double t_next_y, t_next_x, t_next_z;
|
||||
+
|
||||
+ if (dimensionX == 0f) {
|
||||
+ x_inc = 0;
|
||||
+ t_next_x = dimFracX; // don't increment horizontally because the Vec3d is perfectly vertical
|
||||
+ } else if (target.x > start.x) {
|
||||
+ x_inc = 1; // target point is horizontally greater than starting point so increment every
|
||||
+ // step by 1
|
||||
+ intersectCount += MathUtilities.floor(target.x) - x; // increment total amount of intersecting cells
|
||||
+ t_next_x = (float) ((x + 1 - start.x) * dimFracX); // calculate the next horizontal
|
||||
+ // intersection
|
||||
+ // point based on the position inside
|
||||
+ // the first cell
|
||||
+ } else {
|
||||
+ x_inc = -1; // target point is horizontally smaller than starting point so reduce every step
|
||||
+ // by 1
|
||||
+ intersectCount += x - MathUtilities.floor(target.x); // increment total amount of intersecting cells
|
||||
+ t_next_x = (float) ((start.x - x)
|
||||
+ * dimFracX); // calculate the next horizontal
|
||||
+ // intersection point
|
||||
+ // based on the position inside
|
||||
+ // the first cell
|
||||
+ }
|
||||
+
|
||||
+ if (dimensionY == 0f) {
|
||||
+ y_inc = 0;
|
||||
+ t_next_y = dimFracY; // don't increment vertically because the Vec3d is perfectly horizontal
|
||||
+ } else if (target.y > start.y) {
|
||||
+ y_inc = 1; // target point is vertically greater than starting point so increment every
|
||||
+ // step by 1
|
||||
+ intersectCount += MathUtilities.floor(target.y) - y; // increment total amount of intersecting cells
|
||||
+ t_next_y = (float) ((y + 1 - start.y)
|
||||
+ * dimFracY); // calculate the next vertical
|
||||
+ // intersection
|
||||
+ // point based on the position inside
|
||||
+ // the first cell
|
||||
+ } else {
|
||||
+ y_inc = -1; // target point is vertically smaller than starting point so reduce every step
|
||||
+ // by 1
|
||||
+ intersectCount += y - MathUtilities.floor(target.y); // increment total amount of intersecting cells
|
||||
+ t_next_y = (float) ((start.y - y)
|
||||
+ * dimFracY); // calculate the next vertical intersection
|
||||
+ // point
|
||||
+ // based on the position inside
|
||||
+ // the first cell
|
||||
+ }
|
||||
+
|
||||
+ if (dimensionZ == 0f) {
|
||||
+ z_inc = 0;
|
||||
+ t_next_z = dimFracZ; // don't increment vertically because the Vec3d is perfectly horizontal
|
||||
+ } else if (target.z > start.z) {
|
||||
+ z_inc = 1; // target point is vertically greater than starting point so increment every
|
||||
+ // step by 1
|
||||
+ intersectCount += MathUtilities.floor(target.z) - z; // increment total amount of intersecting cells
|
||||
+ t_next_z = (float) ((z + 1 - start.z)
|
||||
+ * dimFracZ); // calculate the next vertical
|
||||
+ // intersection
|
||||
+ // point based on the position inside
|
||||
+ // the first cell
|
||||
+ } else {
|
||||
+ z_inc = -1; // target point is vertically smaller than starting point so reduce every step
|
||||
+ // by 1
|
||||
+ intersectCount += z - MathUtilities.floor(target.z); // increment total amount of intersecting cells
|
||||
+ t_next_z = (float) ((start.z - z)
|
||||
+ * dimFracZ); // calculate the next vertical intersection
|
||||
+ // point
|
||||
+ // based on the position inside
|
||||
+ // the first cell
|
||||
+ }
|
||||
+
|
||||
+ boolean finished = stepRay(start, x, y, z,
|
||||
+ dimFracX, dimFracY, dimFracZ, intersectCount, x_inc, y_inc,
|
||||
+ z_inc, t_next_y, t_next_x, t_next_z);
|
||||
+ provider.cleanup();
|
||||
+ if (finished) {
|
||||
+ cacheResult(targets[0], true);
|
||||
+ return true;
|
||||
+ } else {
|
||||
+ allowRayChecks = true;
|
||||
+ }
|
||||
+ }
|
||||
+ cacheResult(targets[0], false);
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ private boolean stepRay(Vec3d start, int currentX, int currentY,
|
||||
+ int currentZ, double distInX, double distInY,
|
||||
+ double distInZ, int n, int x_inc, int y_inc,
|
||||
+ int z_inc, double t_next_y, double t_next_x,
|
||||
+ double t_next_z) {
|
||||
+ allowWallClipping = true; // initially allow rays to go through walls till they are on the outside
|
||||
+ // iterate through all intersecting cells (n times)
|
||||
+ for (; n > 1; n--) { // n-1 times because we don't want to check the last block
|
||||
+ // towards - where from
|
||||
+
|
||||
+
|
||||
+ // get cached value, 0 means uncached (default)
|
||||
+ int cVal = getCacheValue(currentX, currentY, currentZ);
|
||||
+
|
||||
+ if (cVal == 2 && !allowWallClipping) {
|
||||
+ // block cached as occluding, stop ray
|
||||
+ lastHitBlock[0] = currentX;
|
||||
+ lastHitBlock[1] = currentY;
|
||||
+ lastHitBlock[2] = currentZ;
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ if (cVal == 0) {
|
||||
+ // save current cell
|
||||
+ int chunkX = currentX >> 4;
|
||||
+ int chunkZ = currentZ >> 4;
|
||||
+
|
||||
+ if (!provider.prepareChunk(chunkX, chunkZ)) { // Chunk not ready
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ if (provider.isOpaqueFullCube(currentX, currentY, currentZ)) {
|
||||
+ if (!allowWallClipping) {
|
||||
+ cache.setLastHidden();
|
||||
+ lastHitBlock[0] = currentX;
|
||||
+ lastHitBlock[1] = currentY;
|
||||
+ lastHitBlock[2] = currentZ;
|
||||
+ return false;
|
||||
+ }
|
||||
+ } else {
|
||||
+ // outside of wall, now clipping is not allowed
|
||||
+ allowWallClipping = false;
|
||||
+ cache.setLastVisible();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if(cVal == 1) {
|
||||
+ // outside of wall, now clipping is not allowed
|
||||
+ allowWallClipping = false;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ if (t_next_y < t_next_x && t_next_y < t_next_z) { // next cell is upwards/downwards because the distance to
|
||||
+ // the next vertical
|
||||
+ // intersection point is smaller than to the next horizontal intersection point
|
||||
+ currentY += y_inc; // move up/down
|
||||
+ t_next_y += distInY; // update next vertical intersection point
|
||||
+ } else if (t_next_x < t_next_y && t_next_x < t_next_z) { // next cell is right/left
|
||||
+ currentX += x_inc; // move right/left
|
||||
+ t_next_x += distInX; // update next horizontal intersection point
|
||||
+ } else {
|
||||
+ currentZ += z_inc; // move right/left
|
||||
+ t_next_z += distInZ; // update next horizontal intersection point
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ // -1 = invalid location, 0 = not checked yet, 1 = visible, 2 = occluding
|
||||
+ private int getCacheValue(int x, int y, int z) {
|
||||
+ x -= cameraPos[0];
|
||||
+ y -= cameraPos[1];
|
||||
+ z -= cameraPos[2];
|
||||
+ if (Math.abs(x) > reach - 2 || Math.abs(y) > reach - 2
|
||||
+ || Math.abs(z) > reach - 2) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ // check if target is already known
|
||||
+ return cache.getState(x + reach, y + reach, z + reach);
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ private void cacheResult(int x, int y, int z, boolean result) {
|
||||
+ int cx = x - cameraPos[0] + reach;
|
||||
+ int cy = y - cameraPos[1] + reach;
|
||||
+ int cz = z - cameraPos[2] + reach;
|
||||
+ if (result) {
|
||||
+ cache.setVisible(cx, cy, cz);
|
||||
+ } else {
|
||||
+ cache.setHidden(cx, cy, cz);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void cacheResult(Vec3d vector, boolean result) {
|
||||
+ int cx = MathUtilities.floor(vector.x) - cameraPos[0] + reach;
|
||||
+ int cy = MathUtilities.floor(vector.y) - cameraPos[1] + reach;
|
||||
+ int cz = MathUtilities.floor(vector.z) - cameraPos[2] + reach;
|
||||
+ if (result) {
|
||||
+ cache.setVisible(cx, cy, cz);
|
||||
+ } else {
|
||||
+ cache.setHidden(cx, cy, cz);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void resetCache() {
|
||||
+ this.cache.resetCache();
|
||||
+ }
|
||||
+
|
||||
+ private enum Relative {
|
||||
+ INSIDE, POSITIVE, NEGATIVE;
|
||||
+
|
||||
+ public static Relative from(int min, int max, int pos) {
|
||||
+ if (max > pos && min > pos) {
|
||||
+ return POSITIVE;
|
||||
+ } else if (min < pos && max < pos) {
|
||||
+ return NEGATIVE;
|
||||
+ }
|
||||
+ return INSIDE;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,60 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/logisticscraft/occlusionculling/cache/ArrayOcclusionCache.java
|
||||
@@ -1,0 +_,57 @@
|
||||
+package com.logisticscraft.occlusionculling.cache;
|
||||
+
|
||||
+import java.util.Arrays;
|
||||
+
|
||||
+public class ArrayOcclusionCache implements OcclusionCache {
|
||||
+
|
||||
+ private final int reachX2;
|
||||
+ private final byte[] cache;
|
||||
+ private int positionKey;
|
||||
+ private int entry;
|
||||
+ private int offset;
|
||||
+
|
||||
+ public ArrayOcclusionCache(int reach) {
|
||||
+ this.reachX2 = reach * 2;
|
||||
+ this.cache = new byte[(reachX2 * reachX2 * reachX2) / 4];
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void resetCache() {
|
||||
+ Arrays.fill(cache, (byte) 0);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setVisible(int x, int y, int z) {
|
||||
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
|
||||
+ entry = positionKey / 4;
|
||||
+ offset = (positionKey % 4) * 2;
|
||||
+ cache[entry] |= 1 << offset;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setHidden(int x, int y, int z) {
|
||||
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
|
||||
+ entry = positionKey / 4;
|
||||
+ offset = (positionKey % 4) * 2;
|
||||
+ cache[entry] |= 1 << offset + 1;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getState(int x, int y, int z) {
|
||||
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
|
||||
+ entry = positionKey / 4;
|
||||
+ offset = (positionKey % 4) * 2;
|
||||
+ return cache[entry] >> offset & 3;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setLastVisible() {
|
||||
+ cache[entry] |= 1 << offset;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setLastHidden() {
|
||||
+ cache[entry] |= 1 << offset + 1;
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,20 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/logisticscraft/occlusionculling/cache/OcclusionCache.java
|
||||
@@ -1,0 +_,17 @@
|
||||
+package com.logisticscraft.occlusionculling.cache;
|
||||
+
|
||||
+public interface OcclusionCache {
|
||||
+
|
||||
+ void resetCache();
|
||||
+
|
||||
+ void setVisible(int x, int y, int z);
|
||||
+
|
||||
+ void setHidden(int x, int y, int z);
|
||||
+
|
||||
+ int getState(int x, int y, int z);
|
||||
+
|
||||
+ void setLastHidden();
|
||||
+
|
||||
+ void setLastVisible();
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,28 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/logisticscraft/occlusionculling/util/MathUtilities.java
|
||||
@@ -1,0 +_,25 @@
|
||||
+package com.logisticscraft.occlusionculling.util;
|
||||
+
|
||||
+/**
|
||||
+ * Contains MathHelper methods
|
||||
+ */
|
||||
+public final class MathUtilities {
|
||||
+
|
||||
+ private MathUtilities() {
|
||||
+ }
|
||||
+
|
||||
+ public static int floor(double d) {
|
||||
+ int i = (int) d;
|
||||
+ return d < (double) i ? i - 1 : i;
|
||||
+ }
|
||||
+
|
||||
+ public static int fastFloor(double d) {
|
||||
+ return (int) (d + 1024.0) - 1024;
|
||||
+ }
|
||||
+
|
||||
+ public static int ceil(double d) {
|
||||
+ int i = (int) d;
|
||||
+ return d > (double) i ? i + 1 : i;
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,90 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/com/logisticscraft/occlusionculling/util/Vec3d.java
|
||||
@@ -1,0 +_,87 @@
|
||||
+package com.logisticscraft.occlusionculling.util;
|
||||
+
|
||||
+public class Vec3d {
|
||||
+
|
||||
+ public double x;
|
||||
+ public double y;
|
||||
+ public double z;
|
||||
+
|
||||
+ public Vec3d(double x, double y, double z) {
|
||||
+ this.x = x;
|
||||
+ this.y = y;
|
||||
+ this.z = z;
|
||||
+ }
|
||||
+
|
||||
+ public double getX() {
|
||||
+ return x;
|
||||
+ }
|
||||
+
|
||||
+ public double getY() {
|
||||
+ return y;
|
||||
+ }
|
||||
+
|
||||
+ public double getZ() {
|
||||
+ return z;
|
||||
+ }
|
||||
+
|
||||
+ public void set(double x, double y, double z) {
|
||||
+ this.x = x;
|
||||
+ this.y = y;
|
||||
+ this.z = z;
|
||||
+ }
|
||||
+
|
||||
+ public void setAdd(Vec3d vec, double x, double y, double z) {
|
||||
+ this.x = vec.x + x;
|
||||
+ this.y = vec.y + y;
|
||||
+ this.z = vec.z + z;
|
||||
+ }
|
||||
+
|
||||
+ public Vec3d div(Vec3d rayDir) {
|
||||
+ this.x /= rayDir.x;
|
||||
+ this.z /= rayDir.z;
|
||||
+ this.y /= rayDir.y;
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ public Vec3d normalize() {
|
||||
+ double mag = Math.sqrt(x*x+y*y+z*z);
|
||||
+ this.x /= mag;
|
||||
+ this.y /= mag;
|
||||
+ this.z /= mag;
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
+ public boolean equals(Object other) {
|
||||
+ if (this == other) {
|
||||
+ return true;
|
||||
+ }
|
||||
+ if (!(other instanceof Vec3d)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ Vec3d vec3d = (Vec3d) other;
|
||||
+ if (Double.compare(vec3d.x, x) != 0) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (Double.compare(vec3d.y, y) != 0) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ return Double.compare(vec3d.z, z) == 0;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int hashCode() {
|
||||
+ long l = Double.doubleToLongBits(x);
|
||||
+ int i = (int) (l ^ l >>> 32);
|
||||
+ l = Double.doubleToLongBits(y);
|
||||
+ i = 31 * i + (int) (l ^ l >>> 32);
|
||||
+ l = Double.doubleToLongBits(z);
|
||||
+ i = 31 * i + (int) (l ^ l >>> 32);
|
||||
+ return i;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String toString() {
|
||||
+ return "(" + x + ", " + y + ", " + z + ")";
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,144 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java
|
||||
@@ -1,0 +_,141 @@
|
||||
+package dev.kaiijumc.kaiiju;
|
||||
+
|
||||
+import java.io.File;
|
||||
+import java.io.IOException;
|
||||
+import java.util.Map;
|
||||
+import java.util.HashMap;
|
||||
+import java.util.logging.Level;
|
||||
+
|
||||
+import com.google.common.base.Throwables;
|
||||
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
+import io.github.classgraph.ClassGraph;
|
||||
+import io.github.classgraph.ClassInfo;
|
||||
+import io.github.classgraph.ScanResult;
|
||||
+import org.slf4j.Logger;
|
||||
+
|
||||
+import com.mojang.logging.LogUtils;
|
||||
+import net.minecraft.world.entity.Entity;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.configuration.InvalidConfigurationException;
|
||||
+import org.bukkit.configuration.file.YamlConfiguration;
|
||||
+
|
||||
+@SuppressWarnings("unused")
|
||||
+public class KaiijuEntityLimits {
|
||||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||||
+ private static final File CONFIG_FOLDER = new File("luminol_config");
|
||||
+
|
||||
+ protected static final String HEADER =
|
||||
+ "Per region entity limits for Kaiiju.\n"
|
||||
+ + "If there are more of particular entity type in a region than limit, entity ticking will be throttled.\n"
|
||||
+ + "Example: for Wither limit 100 & 300 Withers in a region -> 100 Withers tick every tick & every Wither ticks every 3 ticks.\n"
|
||||
+ + "Available entities: GlowSquid, Ambient, Bat, Animal, Bee, Cat, Chicken, Cod, Cow, Dolphin, Fish, FishSchool, Fox, Golem, IronGolem, "
|
||||
+ + "MushroomCow, Ocelot, Panda, Parrot, Perchable, Pig, PolarBear, PufferFish, Rabbit, Salmon, Sheep, Snowman, Squid, TropicalFish, Turtle, "
|
||||
+ + "WaterAnimal, Wolf, Allay, Axolotl, Camel, Frog, Tadpole, Goat, Horse, HorseAbstract, HorseChestedAbstract, HorseDonkey, HorseMule, "
|
||||
+ + "HorseSkeleton, HorseZombie, Llama, LlamaTrader, Sniffer, EnderCrystal, EnderDragon, Wither, ArmorStand, Hanging, ItemFrame, Leash, "
|
||||
+ + "Painting, GlowItemFrame, FallingBlock, Item, TNTPrimed, Blaze, CaveSpider, Creeper, Drowned, Enderman, Endermite, Evoker, Ghast, "
|
||||
+ + "GiantZombie, Guardian, GuardianElder, IllagerAbstract, IllagerIllusioner, IllagerWizard, MagmaCube, Monster, MonsterPatrolling, Phantom, "
|
||||
+ + "PigZombie, Pillager, Ravager, Shulker, Silverfish, Skeleton, SkeletonAbstract, SkeletonStray, SkeletonWither, Slime, Spider, Strider, Vex, "
|
||||
+ + "Vindicator, Witch, Zoglin, Zombie, ZombieHusk, ZombieVillager, Hoglin, Piglin, PiglinAbstract, PiglinBrute, Warden, Villager, "
|
||||
+ + "VillagerTrader, Arrow, DragonFireball, Egg, EnderPearl, EnderSignal, EvokerFangs, Fireball, FireballFireball, Fireworks, FishingHook, "
|
||||
+ + "LargeFireball, LlamaSpit, Potion, Projectile, ProjectileThrowable, ShulkerBullet, SmallFireball, Snowball, SpectralArrow, ThrownExpBottle, "
|
||||
+ + "ThrownTrident, TippedArrow, WitherSkull, Raider, ChestBoat, Boat, MinecartAbstract, MinecartChest, MinecartCommandBlock, MinecartContainer, "
|
||||
+ + "MinecartFurnace, MinecartHopper, MinecartMobSpawner, MinecartRideable, MinecartTNT\n";
|
||||
+ protected static final File ENTITY_LIMITS_FILE = new File(CONFIG_FOLDER, "kaiiju_entity_limits.yml");
|
||||
+ public static YamlConfiguration entityLimitsConfig;
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ protected static Map<Class<? extends Entity>, EntityLimit> entityLimits;
|
||||
+
|
||||
+ static final String ENTITY_PREFIX = "Entity";
|
||||
+
|
||||
+ public static void init() {
|
||||
+ init(true);
|
||||
+ }
|
||||
+
|
||||
+ private static void init(boolean setup) {
|
||||
+ entityLimitsConfig = new YamlConfiguration();
|
||||
+
|
||||
+ if (ENTITY_LIMITS_FILE.exists()) {
|
||||
+ try {
|
||||
+ entityLimitsConfig.load(ENTITY_LIMITS_FILE);
|
||||
+ } catch (InvalidConfigurationException ex) {
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, "Could not load kaiiju_entity_limits.yml, please correct your syntax errors", ex);
|
||||
+ throw Throwables.propagate(ex);
|
||||
+ } catch (IOException ignore) {}
|
||||
+ } else {
|
||||
+ if (setup) {
|
||||
+ entityLimitsConfig.options().header(HEADER);
|
||||
+ entityLimitsConfig.options().copyDefaults(true);
|
||||
+ entityLimitsConfig.set("enabled", enabled);
|
||||
+ entityLimitsConfig.set("Axolotl.limit", 1000);
|
||||
+ entityLimitsConfig.set("Axolotl.removal", 2000);
|
||||
+ try {
|
||||
+ entityLimitsConfig.save(ENTITY_LIMITS_FILE);
|
||||
+ } catch (IOException ex) {
|
||||
+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + ENTITY_LIMITS_FILE, ex);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ enabled = entityLimitsConfig.getBoolean("enabled");
|
||||
+
|
||||
+ entityLimits = new Object2ObjectOpenHashMap<>();
|
||||
+ try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("net.minecraft.world.entity").scan()) {
|
||||
+ Map<String, ClassInfo> entityClasses = new HashMap<>();
|
||||
+ for (ClassInfo classInfo : scanResult.getAllClasses()) {
|
||||
+ Class<?> entityClass = Class.forName(classInfo.getName());
|
||||
+ if (Entity.class.isAssignableFrom(entityClass)) {
|
||||
+ String entityName = extractEntityName(entityClass.getSimpleName());
|
||||
+ entityClasses.put(entityName, classInfo);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (String key : entityLimitsConfig.getKeys(false)) {
|
||||
+ if (key.equals("enabled")) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (!entityClasses.containsKey(key)) {
|
||||
+ LOGGER.error("Unknown entity '" + key + "' in kaiiju-entity-limits.yml, skipping");
|
||||
+ continue;
|
||||
+ }
|
||||
+ int limit = entityLimitsConfig.getInt(key + ".limit");
|
||||
+ int removal = entityLimitsConfig.getInt(key + ".removal");
|
||||
+
|
||||
+ if (limit < 1) {
|
||||
+ LOGGER.error(key + " has a limit less than the minimum of 1, ignoring");
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (removal <= limit && removal != -1) {
|
||||
+ LOGGER.error(key + " has a removal limit that is less than or equal to its limit, setting removal to limit * 10");
|
||||
+ removal = limit * 10;
|
||||
+ }
|
||||
+
|
||||
+ entityLimits.put((Class<? extends Entity>) Class.forName(entityClasses.get(key).getName()), new EntityLimit(limit, removal));
|
||||
+ }
|
||||
+ } catch (ClassNotFoundException e) {
|
||||
+ e.printStackTrace();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static EntityLimit getEntityLimit(Entity entity) {
|
||||
+ return entityLimits.get(entity.getClass());
|
||||
+ }
|
||||
+
|
||||
+ private static String extractEntityName(String input) {
|
||||
+ int prefixLength = ENTITY_PREFIX.length();
|
||||
+
|
||||
+ if (input.length() <= prefixLength || !input.startsWith(ENTITY_PREFIX)) {
|
||||
+ return input;
|
||||
+ } else {
|
||||
+ return input.substring(prefixLength);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public record EntityLimit(int limit, int removal) {
|
||||
+ @Override
|
||||
+ public String toString() {
|
||||
+ return "EntityLimit{limit=" + limit + ", removal=" + removal + "}";
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,87 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityThrottler.java
|
||||
@@ -1,0 +_,84 @@
|
||||
+package dev.kaiijumc.kaiiju;
|
||||
+
|
||||
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
+import net.minecraft.world.entity.Entity;
|
||||
+import io.papermc.paper.threadedregions.RegionizedWorldData;
|
||||
+
|
||||
+public class KaiijuEntityThrottler {
|
||||
+ private static class TickInfo {
|
||||
+ int currentTick;
|
||||
+ int continueFrom;
|
||||
+ int toTick;
|
||||
+ int toRemove;
|
||||
+ }
|
||||
+
|
||||
+ public static class EntityThrottlerReturn {
|
||||
+ public boolean skip;
|
||||
+ public boolean remove;
|
||||
+ }
|
||||
+
|
||||
+ private final Object2ObjectOpenHashMap<KaiijuEntityLimits.EntityLimit, TickInfo> entityLimitTickInfoMap = new Object2ObjectOpenHashMap<>();
|
||||
+
|
||||
+ public void tickLimiterStart() {
|
||||
+ for (TickInfo tickInfo : entityLimitTickInfoMap.values()) {
|
||||
+ tickInfo.currentTick = 0;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public EntityThrottlerReturn tickLimiterShouldSkip(Entity entity) {
|
||||
+ EntityThrottlerReturn retVal = new EntityThrottlerReturn();
|
||||
+ if (entity.isRemoved()) return retVal;
|
||||
+ KaiijuEntityLimits.EntityLimit entityLimit = KaiijuEntityLimits.getEntityLimit(entity);
|
||||
+
|
||||
+ if (entityLimit != null) {
|
||||
+ TickInfo tickInfo = entityLimitTickInfoMap.computeIfAbsent(entityLimit, el -> {
|
||||
+ TickInfo newTickInfo = new TickInfo();
|
||||
+ newTickInfo.toTick = entityLimit.limit();
|
||||
+ return newTickInfo;
|
||||
+ });
|
||||
+
|
||||
+ tickInfo.currentTick++;
|
||||
+ if (tickInfo.currentTick <= tickInfo.toRemove && entityLimit.removal() > 0) {
|
||||
+ retVal.skip = false;
|
||||
+ retVal.remove = true;
|
||||
+ return retVal;
|
||||
+ }
|
||||
+
|
||||
+ if (tickInfo.currentTick < tickInfo.continueFrom) {
|
||||
+ retVal.skip = true;
|
||||
+ return retVal;
|
||||
+ }
|
||||
+ if (tickInfo.currentTick - tickInfo.continueFrom < tickInfo.toTick) {
|
||||
+ retVal.skip = false;
|
||||
+ return retVal;
|
||||
+ }
|
||||
+ retVal.skip = true;
|
||||
+ return retVal;
|
||||
+ } else {
|
||||
+ retVal.skip = false;
|
||||
+ return retVal;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void tickLimiterFinish(RegionizedWorldData regionizedWorldData) {
|
||||
+ for (var entry : entityLimitTickInfoMap.entrySet()) {
|
||||
+ KaiijuEntityLimits.EntityLimit entityLimit = entry.getKey();
|
||||
+ TickInfo tickInfo = entry.getValue();
|
||||
+
|
||||
+ int additionals = 0;
|
||||
+ int nextContinueFrom = tickInfo.continueFrom + tickInfo.toTick;
|
||||
+ if (nextContinueFrom >= tickInfo.currentTick) {
|
||||
+ additionals = entityLimit.limit() - (tickInfo.currentTick - tickInfo.continueFrom);
|
||||
+ nextContinueFrom = 0;
|
||||
+ }
|
||||
+ tickInfo.continueFrom = nextContinueFrom;
|
||||
+ tickInfo.toTick = entityLimit.limit() + additionals;
|
||||
+
|
||||
+ if (tickInfo.toRemove == 0 && tickInfo.currentTick > entityLimit.removal()) {
|
||||
+ tickInfo.toRemove = tickInfo.currentTick - entityLimit.removal();
|
||||
+ } else if (tickInfo.toRemove != 0) {
|
||||
+ tickInfo.toRemove = 0;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,155 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/dev/tr7zw/entityculling/CullTask.java
|
||||
@@ -1,0 +_,152 @@
|
||||
+package dev.tr7zw.entityculling;
|
||||
+
|
||||
+import java.util.concurrent.CompletableFuture;
|
||||
+import java.util.concurrent.Executor;
|
||||
+import java.util.concurrent.Executors;
|
||||
+import java.util.concurrent.TimeUnit;
|
||||
+
|
||||
+import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
+import com.logisticscraft.occlusionculling.OcclusionCullingInstance;
|
||||
+import com.logisticscraft.occlusionculling.util.Vec3d;
|
||||
+
|
||||
+import dev.tr7zw.entityculling.versionless.access.Cullable;
|
||||
+import me.earthme.luminol.config.modules.experiment.RayTrackingEntityTrackerConfig;
|
||||
+import net.minecraft.world.entity.Entity;
|
||||
+import net.minecraft.world.entity.decoration.ArmorStand;
|
||||
+import net.minecraft.world.entity.player.Player;
|
||||
+import net.minecraft.world.phys.AABB;
|
||||
+import net.minecraft.world.phys.Vec3;
|
||||
+
|
||||
+public class CullTask implements Runnable {
|
||||
+
|
||||
+ private volatile boolean requestCull = false;
|
||||
+ private volatile boolean scheduleNext = true;
|
||||
+ private volatile boolean inited = false;
|
||||
+
|
||||
+ private final OcclusionCullingInstance culling;
|
||||
+ private final Player checkTarget;
|
||||
+
|
||||
+ private final int hitboxLimit;
|
||||
+
|
||||
+ public long lastCheckedTime = 0;
|
||||
+
|
||||
+ // reused preallocated vars
|
||||
+ private final Vec3d lastPos = new Vec3d(0, 0, 0);
|
||||
+ private final Vec3d aabbMin = new Vec3d(0, 0, 0);
|
||||
+ private final Vec3d aabbMax = new Vec3d(0, 0, 0);
|
||||
+
|
||||
+ private static final Executor backgroundWorker = Executors.newCachedThreadPool(task -> {
|
||||
+ final TickThread worker = new TickThread("EntityCulling") {
|
||||
+ @Override
|
||||
+ public void run() {
|
||||
+ task.run();
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ worker.setDaemon(true);
|
||||
+
|
||||
+ return worker;
|
||||
+ });
|
||||
+
|
||||
+ private final Executor worker;
|
||||
+
|
||||
+ public CullTask(
|
||||
+ OcclusionCullingInstance culling,
|
||||
+ Player checkTarget,
|
||||
+ int hitboxLimit,
|
||||
+ long checkIntervalMs
|
||||
+ ) {
|
||||
+ this.culling = culling;
|
||||
+ this.checkTarget = checkTarget;
|
||||
+ this.hitboxLimit = hitboxLimit;
|
||||
+ this.worker = CompletableFuture.delayedExecutor(checkIntervalMs, TimeUnit.MILLISECONDS, backgroundWorker);
|
||||
+ }
|
||||
+
|
||||
+ public void requestCullSignal() {
|
||||
+ this.requestCull = true;
|
||||
+ }
|
||||
+
|
||||
+ public void signalStop() {
|
||||
+ this.scheduleNext = false;
|
||||
+ }
|
||||
+
|
||||
+ public void setup() {
|
||||
+ if (!this.inited)
|
||||
+ this.inited = true;
|
||||
+ else
|
||||
+ return;
|
||||
+ this.worker.execute(this);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void run() {
|
||||
+ try {
|
||||
+ if (this.checkTarget.tickCount > 10) {
|
||||
+ // getEyePosition can use a fixed delta as its debug only anyway
|
||||
+ Vec3 cameraMC = this.checkTarget.getEyePosition(0);
|
||||
+ if (requestCull || !(cameraMC.x == lastPos.x && cameraMC.y == lastPos.y && cameraMC.z == lastPos.z)) {
|
||||
+ long start = System.currentTimeMillis();
|
||||
+
|
||||
+ requestCull = false;
|
||||
+
|
||||
+ lastPos.set(cameraMC.x, cameraMC.y, cameraMC.z);
|
||||
+ culling.resetCache();
|
||||
+
|
||||
+ cullEntities(cameraMC, lastPos);
|
||||
+
|
||||
+ lastCheckedTime = (System.currentTimeMillis() - start);
|
||||
+ }
|
||||
+ }
|
||||
+ }finally {
|
||||
+ if (this.scheduleNext) {
|
||||
+ this.worker.execute(this);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void cullEntities(Vec3 cameraMC, Vec3d camera) {
|
||||
+ for (Entity entity : this.checkTarget.level().getEntities().getAll()) {
|
||||
+ if (!(entity instanceof Cullable cullable)) {
|
||||
+ continue; // Not sure how this could happen outside from mixin screwing up the inject into
|
||||
+ // Entity
|
||||
+ }
|
||||
+
|
||||
+ if (entity.getType().skipRaytracningCheck) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (!cullable.isForcedVisible()) {
|
||||
+ if (entity.isCurrentlyGlowing() || isSkippableArmorstand(entity)) {
|
||||
+ cullable.setCulled(false);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (!entity.position().closerThan(cameraMC, me.earthme.luminol.config.modules.experiment.RayTrackingEntityTrackerConfig.tracingDistance)) {
|
||||
+ cullable.setCulled(false); // If your entity view distance is larger than tracingDistance just
|
||||
+ // render it
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ AABB boundingBox = entity.getBoundingBox();
|
||||
+ if (boundingBox.getXsize() > hitboxLimit || boundingBox.getYsize() > hitboxLimit
|
||||
+ || boundingBox.getZsize() > hitboxLimit) {
|
||||
+ cullable.setCulled(false); // To big to bother to cull
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ aabbMin.set(boundingBox.minX, boundingBox.minY, boundingBox.minZ);
|
||||
+ aabbMax.set(boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ);
|
||||
+
|
||||
+ boolean visible = culling.isAABBVisible(aabbMin, aabbMax, camera);
|
||||
+
|
||||
+ cullable.setCulled(!visible);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private boolean isSkippableArmorstand(Entity entity) {
|
||||
+ if (!RayTrackingEntityTrackerConfig.skipMarkerArmorStands)
|
||||
+ return false;
|
||||
+ return entity instanceof ArmorStand && entity.isInvisible();
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,46 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/dev/tr7zw/entityculling/DefaultChunkDataProvider.java
|
||||
@@ -1,0 +_,43 @@
|
||||
+package dev.tr7zw.entityculling;
|
||||
+
|
||||
+import com.logisticscraft.occlusionculling.DataProvider;
|
||||
+import net.minecraft.core.BlockPos;
|
||||
+import net.minecraft.world.level.Level;
|
||||
+import net.minecraft.world.level.block.Blocks;
|
||||
+import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
+import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
+
|
||||
+public class DefaultChunkDataProvider implements DataProvider {
|
||||
+ private final Level level;
|
||||
+
|
||||
+ public DefaultChunkDataProvider(Level level) {
|
||||
+ this.level = level;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean prepareChunk(int chunkX, int chunkZ) {
|
||||
+ return this.level.getChunkIfLoaded(chunkX, chunkZ) != null;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isOpaqueFullCube(int x, int y, int z) {
|
||||
+ BlockPos pos = new BlockPos(x, y, z);
|
||||
+
|
||||
+ final ChunkAccess access = this.level.getChunkIfLoaded(pos);
|
||||
+ if (access == null) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ if (this.level.isOutsideBuildHeight(pos)) {
|
||||
+ return Blocks.VOID_AIR.defaultBlockState().isSolidRender();
|
||||
+ } else {
|
||||
+ return access.getBlockState(pos).isSolidRender();// 好孩子不要学坏叔叔这样绕过异步拦截()
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void cleanup() {
|
||||
+ DataProvider.super.cleanup();
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,20 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/dev/tr7zw/entityculling/versionless/access/Cullable.java
|
||||
@@ -1,0 +_,17 @@
|
||||
+package dev.tr7zw.entityculling.versionless.access;
|
||||
+
|
||||
+public interface Cullable {
|
||||
+
|
||||
+ public void setTimeout();
|
||||
+
|
||||
+ public boolean isForcedVisible();
|
||||
+
|
||||
+ public void setCulled(boolean value);
|
||||
+
|
||||
+ public boolean isCulled();
|
||||
+
|
||||
+ public void setOutOfCamera(boolean value);
|
||||
+
|
||||
+ public boolean isOutOfCamera();
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,136 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java
|
||||
@@ -1,0 +_,133 @@
|
||||
+package gg.pufferfish.pufferfish.sentry;
|
||||
+
|
||||
+import com.google.common.reflect.TypeToken;
|
||||
+import com.google.gson.Gson;
|
||||
+import io.sentry.Breadcrumb;
|
||||
+import io.sentry.Sentry;
|
||||
+import io.sentry.SentryEvent;
|
||||
+import io.sentry.SentryLevel;
|
||||
+import io.sentry.protocol.Message;
|
||||
+import io.sentry.protocol.User;
|
||||
+
|
||||
+import java.util.Map;
|
||||
+
|
||||
+import me.earthme.luminol.config.modules.misc.SentryConfig;
|
||||
+import org.apache.logging.log4j.Level;
|
||||
+import org.apache.logging.log4j.LogManager;
|
||||
+import org.apache.logging.log4j.Marker;
|
||||
+import org.apache.logging.log4j.core.LogEvent;
|
||||
+import org.apache.logging.log4j.core.Logger;
|
||||
+import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||
+import org.apache.logging.log4j.core.filter.AbstractFilter;
|
||||
+
|
||||
+public class PufferfishSentryAppender extends AbstractAppender {
|
||||
+
|
||||
+ private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(PufferfishSentryAppender.class.getSimpleName());
|
||||
+ private static final Gson GSON = new Gson();
|
||||
+ private final Level logLevel;
|
||||
+
|
||||
+ public PufferfishSentryAppender(Level logLevel) {
|
||||
+ super("PufferfishSentryAdapter", new SentryFilter(), null);
|
||||
+ this.logLevel = logLevel;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void append(LogEvent logEvent) {
|
||||
+ if (logEvent.getLevel().isMoreSpecificThan(logLevel) && (logEvent.getThrown() != null || !SentryConfig.onlyLogThrown)) {
|
||||
+ try {
|
||||
+ logException(logEvent);
|
||||
+ } catch (Exception e) {
|
||||
+ logger.warn("Failed to log event with sentry", e);
|
||||
+ }
|
||||
+ } else {
|
||||
+ try {
|
||||
+ logBreadcrumb(logEvent);
|
||||
+ } catch (Exception e) {
|
||||
+ logger.warn("Failed to log event with sentry", e);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void logException(LogEvent e) {
|
||||
+ SentryEvent event = new SentryEvent(e.getThrown());
|
||||
+
|
||||
+ Message sentryMessage = new Message();
|
||||
+ sentryMessage.setMessage(e.getMessage().getFormattedMessage());
|
||||
+
|
||||
+ event.setThrowable(e.getThrown());
|
||||
+ event.setLevel(getLevel(e.getLevel()));
|
||||
+ event.setLogger(e.getLoggerName());
|
||||
+ event.setTransaction(e.getLoggerName());
|
||||
+ event.setExtra("thread_name", e.getThreadName());
|
||||
+
|
||||
+ boolean hasContext = e.getContextData() != null;
|
||||
+
|
||||
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) {
|
||||
+ User user = new User();
|
||||
+ user.setId(e.getContextData().getValue("pufferfishsentry_playerid"));
|
||||
+ user.setUsername(e.getContextData().getValue("pufferfishsentry_playername"));
|
||||
+ event.setUser(user);
|
||||
+ }
|
||||
+
|
||||
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) {
|
||||
+ event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname"));
|
||||
+ event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion"));
|
||||
+ event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname"));
|
||||
+ }
|
||||
+
|
||||
+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) {
|
||||
+ Map<String, String> eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken<Map<String, String>>() {
|
||||
+ }.getType());
|
||||
+ if (eventFields != null) {
|
||||
+ event.setExtra("event", eventFields);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ Sentry.captureEvent(event);
|
||||
+ }
|
||||
+
|
||||
+ private void logBreadcrumb(LogEvent e) {
|
||||
+ Breadcrumb breadcrumb = new Breadcrumb();
|
||||
+
|
||||
+ breadcrumb.setLevel(getLevel(e.getLevel()));
|
||||
+ breadcrumb.setCategory(e.getLoggerName());
|
||||
+ breadcrumb.setType(e.getLoggerName());
|
||||
+ breadcrumb.setMessage(e.getMessage().getFormattedMessage());
|
||||
+
|
||||
+ Sentry.addBreadcrumb(breadcrumb);
|
||||
+ }
|
||||
+
|
||||
+ private SentryLevel getLevel(Level level) {
|
||||
+ return switch (level.getStandardLevel()) {
|
||||
+ case TRACE, DEBUG -> SentryLevel.DEBUG;
|
||||
+ case WARN -> SentryLevel.WARNING;
|
||||
+ case ERROR -> SentryLevel.ERROR;
|
||||
+ case FATAL -> SentryLevel.FATAL;
|
||||
+ default -> SentryLevel.INFO;
|
||||
+ };
|
||||
+ }
|
||||
+
|
||||
+ private static class SentryFilter extends AbstractFilter {
|
||||
+
|
||||
+ @Override
|
||||
+ public Result filter(Logger logger, Level level, Marker marker, String msg,
|
||||
+ Object... params) {
|
||||
+ return this.filter(logger.getName());
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
|
||||
+ return this.filter(logger.getName());
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public Result filter(LogEvent event) {
|
||||
+ return this.filter(event == null ? null : event.getLoggerName());
|
||||
+ }
|
||||
+
|
||||
+ private Result filter(String loggerName) {
|
||||
+ return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY
|
||||
+ : Result.NEUTRAL;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,47 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java
|
||||
@@ -1,0 +_,44 @@
|
||||
+package gg.pufferfish.pufferfish.sentry;
|
||||
+
|
||||
+import io.sentry.Sentry;
|
||||
+import org.apache.logging.log4j.Level;
|
||||
+import org.apache.logging.log4j.LogManager;
|
||||
+import org.apache.logging.log4j.Logger;
|
||||
+
|
||||
+public class SentryManager {
|
||||
+
|
||||
+ private static final Logger logger = LogManager.getLogger(SentryManager.class);
|
||||
+
|
||||
+ private SentryManager() {
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+ private static boolean initialized = false;
|
||||
+
|
||||
+ public static synchronized void init(Level logLevel) {
|
||||
+ if (initialized) {
|
||||
+ return;
|
||||
+ }
|
||||
+ if (logLevel == null) {
|
||||
+ logger.error("Invalid log level, defaulting to WARN.");
|
||||
+ logLevel = Level.WARN;
|
||||
+ }
|
||||
+ try {
|
||||
+ initialized = true;
|
||||
+
|
||||
+ Sentry.init(options -> {
|
||||
+ options.setDsn(me.earthme.luminol.config.modules.misc.SentryConfig.sentryDsn);
|
||||
+ options.setMaxBreadcrumbs(100);
|
||||
+ });
|
||||
+
|
||||
+ PufferfishSentryAppender appender = new PufferfishSentryAppender(logLevel);
|
||||
+ appender.start();
|
||||
+ ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(appender);
|
||||
+ logger.info("Sentry logging started!");
|
||||
+ } catch (Exception e) {
|
||||
+ logger.warn("Failed to initialize sentry!", e);
|
||||
+ initialized = false;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,30 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/api/impl/RegionStatsImpl.java
|
||||
@@ -1,0 +_,27 @@
|
||||
+package me.earthme.luminol.api.impl;
|
||||
+
|
||||
+import io.papermc.paper.threadedregions.TickRegions;
|
||||
+import me.earthme.luminol.api.RegionStats;
|
||||
+
|
||||
+public class RegionStatsImpl implements RegionStats {
|
||||
+ private final TickRegions.RegionStats internal;
|
||||
+
|
||||
+ public RegionStatsImpl(TickRegions.RegionStats internal) {
|
||||
+ this.internal = internal;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getEntityCount() {
|
||||
+ return this.internal.getEntityCount();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getPlayerCount() {
|
||||
+ return this.internal.getPlayerCount();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int getChunkCount() {
|
||||
+ return this.internal.getChunkCount();
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,50 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/api/impl/ThreadedRegionImpl.java
|
||||
@@ -1,0 +_,47 @@
|
||||
+package me.earthme.luminol.api.impl;
|
||||
+
|
||||
+import io.papermc.paper.threadedregions.ThreadedRegionizer;
|
||||
+import io.papermc.paper.threadedregions.TickRegions;
|
||||
+import me.earthme.luminol.api.ThreadedRegion;
|
||||
+import me.earthme.luminol.api.TickRegionData;
|
||||
+import net.minecraft.world.level.ChunkPos;
|
||||
+import org.bukkit.Location;
|
||||
+import org.bukkit.World;
|
||||
+
|
||||
+import javax.annotation.Nullable;
|
||||
+
|
||||
+public class ThreadedRegionImpl implements ThreadedRegion {
|
||||
+ private final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> internal;
|
||||
+
|
||||
+ public ThreadedRegionImpl(ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> internal) {
|
||||
+ this.internal = internal;
|
||||
+ }
|
||||
+
|
||||
+ @Nullable
|
||||
+ @Override
|
||||
+ public Location getCenterChunkPos() {
|
||||
+ final ChunkPos centerChunkPos = this.internal.getCenterChunk();
|
||||
+
|
||||
+ if (centerChunkPos == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ return new Location(this.internal.regioniser.world.getWorld(), centerChunkPos.getMiddleBlockX(), 0, centerChunkPos.getMiddleBlockZ());
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public double getDeadSectionPercent() {
|
||||
+ return this.internal.getDeadSectionPercent();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public TickRegionData getTickRegionData() {
|
||||
+ return new TickRegionDataImpl(this.internal.getData());
|
||||
+ }
|
||||
+
|
||||
+ @Nullable
|
||||
+ @Override
|
||||
+ public World getWorld() {
|
||||
+ return this.internal.regioniser.world.getWorld();
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,56 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/api/impl/ThreadedRegionizerImpl.java
|
||||
@@ -1,0 +_,53 @@
|
||||
+package me.earthme.luminol.api.impl;
|
||||
+
|
||||
+import io.papermc.paper.threadedregions.TickRegions;
|
||||
+import me.earthme.luminol.api.ThreadedRegion;
|
||||
+import me.earthme.luminol.api.ThreadedRegionizer;
|
||||
+import net.minecraft.server.level.ServerLevel;
|
||||
+
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.Collection;
|
||||
+import java.util.List;
|
||||
+
|
||||
+public class ThreadedRegionizerImpl implements ThreadedRegionizer {
|
||||
+ private final ServerLevel internal;
|
||||
+
|
||||
+ public ThreadedRegionizerImpl(ServerLevel internal) {
|
||||
+ this.internal = internal;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public Collection<ThreadedRegion> getAllRegions() {
|
||||
+ final List<ThreadedRegion> ret = new ArrayList<>();
|
||||
+
|
||||
+ this.internal.regioniser.computeForAllRegions(region -> {
|
||||
+ final ThreadedRegion wrapped = new ThreadedRegionImpl(region);
|
||||
+
|
||||
+ ret.add(wrapped);
|
||||
+ });
|
||||
+
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public ThreadedRegion getAtSynchronized(int chunkX, int chunkZ) {
|
||||
+ final io.papermc.paper.threadedregions.ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> got = this.internal.regioniser.getRegionAtSynchronised(chunkX, chunkZ);
|
||||
+
|
||||
+ if (got == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ return new ThreadedRegionImpl(got);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public ThreadedRegion getAtUnSynchronized(int chunkX, int chunkZ) {
|
||||
+ final io.papermc.paper.threadedregions.ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> got = this.internal.regioniser.getRegionAtUnsynchronised(chunkX, chunkZ);
|
||||
+
|
||||
+ if (got == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ return new ThreadedRegionImpl(got);
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,33 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/api/impl/TickRegionDataImpl.java
|
||||
@@ -1,0 +_,30 @@
|
||||
+package me.earthme.luminol.api.impl;
|
||||
+
|
||||
+import io.papermc.paper.threadedregions.TickRegions;
|
||||
+import me.earthme.luminol.api.RegionStats;
|
||||
+import me.earthme.luminol.api.TickRegionData;
|
||||
+import org.bukkit.World;
|
||||
+
|
||||
+public class TickRegionDataImpl implements TickRegionData {
|
||||
+ private final TickRegions.TickRegionData internal;
|
||||
+
|
||||
+ public TickRegionDataImpl(TickRegions.TickRegionData internal) {
|
||||
+ this.internal = internal;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public World getWorld() {
|
||||
+ return this.internal.world.getWorld();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public long getCurrentTickCount() {
|
||||
+ return this.internal.getCurrentTick();
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public RegionStats getRegionStats() {
|
||||
+ return new RegionStatsImpl(this.internal.getRegionStats());
|
||||
+ }
|
||||
+
|
||||
+}
|
||||
@@ -0,0 +1,73 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java
|
||||
@@ -1,0 +_,70 @@
|
||||
+package me.earthme.luminol.commands;
|
||||
+
|
||||
+import me.earthme.luminol.config.LuminolConfig;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.kyori.adventure.text.format.TextColor;
|
||||
+import org.bukkit.Location;
|
||||
+import org.bukkit.command.Command;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+import org.jetbrains.annotations.Nullable;
|
||||
+
|
||||
+import java.util.ArrayList;
|
||||
+import java.util.List;
|
||||
+
|
||||
+public class LuminolConfigCommand extends Command {
|
||||
+ public LuminolConfigCommand(){
|
||||
+ super("luminolconfig");
|
||||
+ this.setPermission("luminol.commands.luminolconfig");
|
||||
+ this.setDescription("Manage config file");
|
||||
+ this.setUsage("/luminolconfig");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args, @Nullable Location location) throws IllegalArgumentException {
|
||||
+ final List<String> result = new ArrayList<>();
|
||||
+
|
||||
+ if (args.length == 1){
|
||||
+ result.add("reload");
|
||||
+ }
|
||||
+
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
|
||||
+ if (!this.testPermission(sender)){
|
||||
+ sender.sendMessage(Component
|
||||
+ .text("No permission to execute this command!")
|
||||
+ .color(TextColor.color(255,0,0))
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ if (args.length < 1){
|
||||
+ sender.sendMessage(
|
||||
+ Component
|
||||
+ .text("Wrong use!\n")
|
||||
+ .color(TextColor.color(255,0,0))
|
||||
+ );
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ switch (args[0]){
|
||||
+ case "reload" -> {
|
||||
+ LuminolConfig.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
|
||||
+ Component
|
||||
+ .text("Reloaded config file!")
|
||||
+ .color(TextColor.color(0,255,0))
|
||||
+ ));
|
||||
+ }
|
||||
+
|
||||
+ default -> sender.sendMessage(
|
||||
+ Component
|
||||
+ .text("Unknown action!\n")
|
||||
+ .color(TextColor.color(255,0,0))
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,50 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/commands/MembarCommand.java
|
||||
@@ -1,0 +_,47 @@
|
||||
+package me.earthme.luminol.commands;
|
||||
+
|
||||
+import me.earthme.luminol.config.modules.misc.MembarConfig;
|
||||
+import me.earthme.luminol.functions.GlobalServerMemoryBar;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.kyori.adventure.text.format.TextColor;
|
||||
+import org.bukkit.command.Command;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.bukkit.entity.Player;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+public class MembarCommand extends Command {
|
||||
+ public MembarCommand(@NotNull String name) {
|
||||
+ super(name);
|
||||
+ this.setPermission("luminol.commands.membar");
|
||||
+ this.setDescription("Show the memory usage through a bossbar");
|
||||
+ this.setUsage("/membar");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
|
||||
+ if (!testPermission(sender)){
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (!MembarConfig.memoryBarEnabled){
|
||||
+ sender.sendMessage(Component.text("Membar was already disabled!").color(TextColor.color(255,0,0)));
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (!(sender instanceof Player player)){
|
||||
+ sender.sendMessage(Component.text("Only player can use this command!").color(TextColor.color(255,0,0)));
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (GlobalServerMemoryBar.isPlayerVisible(player)) {
|
||||
+ player.sendMessage(Component.text("Disabled mem bar").color(TextColor.color(0,255,0)));
|
||||
+ GlobalServerMemoryBar.setVisibilityForPlayer(player,false);
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ player.sendMessage(Component.text("Enabled mem bar").color(TextColor.color(0,255,0)));
|
||||
+ GlobalServerMemoryBar.setVisibilityForPlayer(player,true);
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,50 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/commands/RegionBarCommand.java
|
||||
@@ -1,0 +_,47 @@
|
||||
+package me.earthme.luminol.commands;
|
||||
+
|
||||
+import me.earthme.luminol.config.modules.misc.RegionBarConfig;
|
||||
+import me.earthme.luminol.functions.GlobalServerRegionBar;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.kyori.adventure.text.format.TextColor;
|
||||
+import org.bukkit.command.Command;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.bukkit.entity.Player;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+public class RegionBarCommand extends Command {
|
||||
+ public RegionBarCommand(@NotNull String name) {
|
||||
+ super(name);
|
||||
+ this.setPermission("luminol.commands.regionbar");
|
||||
+ this.setDescription("Show info about your current region through a bossbar");
|
||||
+ this.setUsage("/regionbar");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
|
||||
+ if (!testPermission(sender)) {
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (!RegionBarConfig.regionbarEnabled) {
|
||||
+ sender.sendMessage(Component.text("Regionbar was already disabled!").color(TextColor.color(255, 0, 0)));
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (!(sender instanceof Player player)) {
|
||||
+ sender.sendMessage(Component.text("Only player can use this command!").color(TextColor.color(255, 0, 0)));
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (GlobalServerRegionBar.isPlayerVisible(player)) {
|
||||
+ player.sendMessage(Component.text("Disabled region bar").color(TextColor.color(0, 255, 0)));
|
||||
+ GlobalServerRegionBar.setVisibilityForPlayer(player, false);
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ player.sendMessage(Component.text("Enabled region bar").color(TextColor.color(0, 255, 0)));
|
||||
+ GlobalServerRegionBar.setVisibilityForPlayer(player, true);
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,53 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/commands/TpsBarCommand.java
|
||||
@@ -1,0 +_,50 @@
|
||||
+package me.earthme.luminol.commands;
|
||||
+
|
||||
+import me.earthme.luminol.config.modules.misc.TpsBarConfig;
|
||||
+import me.earthme.luminol.functions.GlobalServerTpsBar;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.kyori.adventure.text.format.TextColor;
|
||||
+import net.kyori.adventure.util.RGBLike;
|
||||
+import org.bukkit.ChatColor;
|
||||
+import org.bukkit.Color;
|
||||
+import org.bukkit.command.Command;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.bukkit.entity.Player;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+public class TpsBarCommand extends Command {
|
||||
+ public TpsBarCommand(@NotNull String name) {
|
||||
+ super(name);
|
||||
+ this.setPermission("luminol.commands.tpsbar");
|
||||
+ this.setDescription("Show the tps and mspt through a bossbar");
|
||||
+ this.setUsage("/tpsbar");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
|
||||
+ if (!testPermission(sender)){
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (!TpsBarConfig.tpsbarEnabled){
|
||||
+ sender.sendMessage(Component.text("Tpsbar was already disabled!").color(TextColor.color(255,0,0)));
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (!(sender instanceof Player player)){
|
||||
+ sender.sendMessage(Component.text("Only player can use this command!").color(TextColor.color(255,0,0)));
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (GlobalServerTpsBar.isPlayerVisible(player)) {
|
||||
+ player.sendMessage(Component.text("Disabled tps bar").color(TextColor.color(0,255,0)));
|
||||
+ GlobalServerTpsBar.setVisibilityForPlayer(player,false);
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ player.sendMessage(Component.text("Enabled tps bar").color(TextColor.color(0,255,0)));
|
||||
+ GlobalServerTpsBar.setVisibilityForPlayer(player,true);
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,14 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/ConfigInfo.java
|
||||
@@ -1,0 +_,11 @@
|
||||
+package me.earthme.luminol.config;
|
||||
+
|
||||
+import java.lang.annotation.Retention;
|
||||
+import java.lang.annotation.RetentionPolicy;
|
||||
+
|
||||
+@Retention(RetentionPolicy.RUNTIME)
|
||||
+public @interface ConfigInfo {
|
||||
+ String baseName();
|
||||
+
|
||||
+ String comments() default "";
|
||||
+}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/DoNotLoad.java
|
||||
@@ -1,0 +_,8 @@
|
||||
+package me.earthme.luminol.config;
|
||||
+
|
||||
+import java.lang.annotation.Retention;
|
||||
+import java.lang.annotation.RetentionPolicy;
|
||||
+
|
||||
+@Retention(RetentionPolicy.RUNTIME)
|
||||
+public @interface DoNotLoad {
|
||||
+}
|
||||
@@ -0,0 +1,22 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/EnumConfigCategory.java
|
||||
@@ -1,0 +_,19 @@
|
||||
+package me.earthme.luminol.config;
|
||||
+
|
||||
+public enum EnumConfigCategory {
|
||||
+ OPTIMIZATIONS("optimizations"),
|
||||
+ FIXES("fixes"),
|
||||
+ MISC("misc"),
|
||||
+ GAMEPLAY("gameplay"),
|
||||
+ EXPERIMENT("experiment");
|
||||
+
|
||||
+ private final String baseKeyName;
|
||||
+
|
||||
+ EnumConfigCategory(String baseKeyName) {
|
||||
+ this.baseKeyName = baseKeyName;
|
||||
+ }
|
||||
+
|
||||
+ public String getBaseKeyName() {
|
||||
+ return this.baseKeyName;
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,11 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/HotReloadUnsupported.java
|
||||
@@ -1,0 +_,8 @@
|
||||
+package me.earthme.luminol.config;
|
||||
+
|
||||
+import java.lang.annotation.Retention;
|
||||
+import java.lang.annotation.RetentionPolicy;
|
||||
+
|
||||
+@Retention(RetentionPolicy.RUNTIME)
|
||||
+public @interface HotReloadUnsupported {
|
||||
+}
|
||||
@@ -0,0 +1,25 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/IConfigModule.java
|
||||
@@ -1,0 +_,22 @@
|
||||
+package me.earthme.luminol.config;
|
||||
+
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+public interface IConfigModule {
|
||||
+
|
||||
+ EnumConfigCategory getCategory();
|
||||
+
|
||||
+ String getBaseName();
|
||||
+
|
||||
+ default void onLoaded(CommentedFileConfig configInstance) {}
|
||||
+
|
||||
+ default <T> T get(String keyName, T defaultValue, @NotNull CommentedFileConfig config){
|
||||
+ if (!config.contains(keyName)){
|
||||
+ config.set(keyName,defaultValue);
|
||||
+ return defaultValue;
|
||||
+ }
|
||||
+
|
||||
+ return config.get(keyName);
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,229 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/LuminolConfig.java
|
||||
@@ -1,0 +_,226 @@
|
||||
+package me.earthme.luminol.config;
|
||||
+
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import io.papermc.paper.threadedregions.RegionizedServer;
|
||||
+import me.earthme.luminol.commands.LuminolConfigCommand;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.jetbrains.annotations.Contract;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+import java.io.File;
|
||||
+import java.io.IOException;
|
||||
+import java.lang.reflect.Field;
|
||||
+import java.lang.reflect.InvocationTargetException;
|
||||
+import java.lang.reflect.Modifier;
|
||||
+import java.net.JarURLConnection;
|
||||
+import java.net.URL;
|
||||
+import java.net.URLDecoder;
|
||||
+import java.nio.charset.StandardCharsets;
|
||||
+import java.util.*;
|
||||
+import java.util.concurrent.CompletableFuture;
|
||||
+import java.util.jar.JarEntry;
|
||||
+import java.util.jar.JarFile;
|
||||
+import org.apache.logging.log4j.LogManager;
|
||||
+import org.apache.logging.log4j.Logger;
|
||||
+
|
||||
+public class LuminolConfig {
|
||||
+ public static final Logger logger = LogManager.getLogger();
|
||||
+ private static final File baseConfigFolder = new File("luminol_config");
|
||||
+ private static final File baseConfigFile = new File(baseConfigFolder,"luminol_global_config.toml");
|
||||
+ private static final Set<IConfigModule> allInstanced = new HashSet<>();
|
||||
+ private static CommentedFileConfig configFileInstance;
|
||||
+ public static boolean alreadyInited = false;
|
||||
+
|
||||
+ public static void setupLatch(){
|
||||
+ Bukkit.getCommandMap().register("luminolconfig","luminol",new LuminolConfigCommand());
|
||||
+ alreadyInited = true;
|
||||
+ }
|
||||
+
|
||||
+ public static void reload(){
|
||||
+ RegionizedServer.ensureGlobalTickThread("Reload luminol config off global region thread!");
|
||||
+
|
||||
+ dropAllInstanced();
|
||||
+ try {
|
||||
+ preLoadConfig();
|
||||
+ finalizeLoadConfig();
|
||||
+ }catch (Exception e){
|
||||
+ logger.error(e);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Contract(" -> new")
|
||||
+ public static @NotNull CompletableFuture<Void> reloadAsync(){
|
||||
+ return CompletableFuture.runAsync(LuminolConfig::reload,task -> RegionizedServer.getInstance().addTask(() -> {
|
||||
+ try{
|
||||
+ task.run();
|
||||
+ }catch (Exception e){
|
||||
+ logger.error(e);
|
||||
+ }
|
||||
+ }));
|
||||
+ }
|
||||
+
|
||||
+ public static void dropAllInstanced(){
|
||||
+ allInstanced.clear();
|
||||
+ }
|
||||
+
|
||||
+ public static void finalizeLoadConfig() {
|
||||
+ for (IConfigModule module : allInstanced) {
|
||||
+ module.onLoaded(configFileInstance);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static void preLoadConfig() throws IOException {
|
||||
+ baseConfigFolder.mkdirs();
|
||||
+
|
||||
+ if (!baseConfigFile.exists()){
|
||||
+ baseConfigFile.createNewFile();
|
||||
+ }
|
||||
+
|
||||
+ configFileInstance = CommentedFileConfig.ofConcurrent(baseConfigFile);
|
||||
+
|
||||
+ configFileInstance.load();
|
||||
+
|
||||
+ try {
|
||||
+ instanceAllModule();
|
||||
+ loadAllModules();
|
||||
+ }catch (Exception e){
|
||||
+ logger.error("Failed to load config modules!",e);
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+
|
||||
+ configFileInstance.save();
|
||||
+ }
|
||||
+
|
||||
+ private static void loadAllModules() throws IllegalAccessException {
|
||||
+ for (IConfigModule instanced : allInstanced){
|
||||
+ loadForSingle(instanced);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void instanceAllModule() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
||||
+ for (Class<?> clazz : getClasses("me.earthme.luminol.config.modules")){
|
||||
+ if (IConfigModule.class.isAssignableFrom(clazz)){
|
||||
+ allInstanced.add((IConfigModule) clazz.getConstructor().newInstance());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void loadForSingle(@NotNull IConfigModule singleConfigModule) throws IllegalAccessException {
|
||||
+ final EnumConfigCategory category = singleConfigModule.getCategory();
|
||||
+
|
||||
+ Field[] fields = singleConfigModule.getClass().getDeclaredFields();
|
||||
+
|
||||
+ for (Field field : fields) {
|
||||
+ int modifiers = field.getModifiers();
|
||||
+ if (Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
|
||||
+ boolean skipLoad = field.getAnnotation(DoNotLoad.class) != null || (alreadyInited && field.getAnnotation(HotReloadUnsupported.class) != null);
|
||||
+ ConfigInfo configInfo = field.getAnnotation(ConfigInfo.class);
|
||||
+
|
||||
+ if (skipLoad || configInfo == null){
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final String fullConfigKeyName = category.getBaseKeyName() + "." + singleConfigModule.getBaseName() + "." + configInfo.baseName();
|
||||
+
|
||||
+ field.setAccessible(true);
|
||||
+ final Object currentValue = field.get(null);
|
||||
+
|
||||
+ if (!configFileInstance.contains(fullConfigKeyName)){
|
||||
+ if (currentValue == null){
|
||||
+ throw new UnsupportedOperationException("Config " + singleConfigModule.getBaseName() + "tried to add an null default value!");
|
||||
+ }
|
||||
+
|
||||
+ final String comments = configInfo.comments();
|
||||
+
|
||||
+ if (!comments.isBlank()){
|
||||
+ configFileInstance.setComment(fullConfigKeyName,comments);
|
||||
+ }
|
||||
+
|
||||
+ configFileInstance.add(fullConfigKeyName,currentValue);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ final Object actuallyValue = configFileInstance.get(fullConfigKeyName);
|
||||
+ field.set(null,actuallyValue);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static @NotNull Set<Class<?>> getClasses(String pack) {
|
||||
+ Set<Class<?>> classes = new LinkedHashSet<>();
|
||||
+ String packageDirName = pack.replace('.', '/');
|
||||
+ Enumeration<URL> dirs;
|
||||
+
|
||||
+ try {
|
||||
+ dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
|
||||
+ while (dirs.hasMoreElements()) {
|
||||
+ URL url = dirs.nextElement();
|
||||
+ String protocol = url.getProtocol();
|
||||
+ if ("file".equals(protocol)) {
|
||||
+ String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
|
||||
+ findClassesInPackageByFile(pack, filePath, classes);
|
||||
+ } else if ("jar".equals(protocol)) {
|
||||
+ JarFile jar;
|
||||
+ try {
|
||||
+ jar = ((JarURLConnection) url.openConnection()).getJarFile();
|
||||
+ Enumeration<JarEntry> entries = jar.entries();
|
||||
+ findClassesInPackageByJar(pack, entries, packageDirName, classes);
|
||||
+ } catch (IOException e) {
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ } catch (IOException e) {
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+
|
||||
+ return classes;
|
||||
+ }
|
||||
+
|
||||
+ private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
|
||||
+ File dir = new File(packagePath);
|
||||
+
|
||||
+ if (!dir.exists() || !dir.isDirectory()) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class"));
|
||||
+ if (dirfiles != null) {
|
||||
+ for (File file : dirfiles) {
|
||||
+ if (file.isDirectory()) {
|
||||
+ findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
|
||||
+ } else {
|
||||
+ String className = file.getName().substring(0, file.getName().length() - 6);
|
||||
+ try {
|
||||
+ classes.add(Class.forName(packageName + '.' + className));
|
||||
+ } catch (ClassNotFoundException e) {
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
|
||||
+ while (entries.hasMoreElements()) {
|
||||
+ JarEntry entry = entries.nextElement();
|
||||
+ String name = entry.getName();
|
||||
+ if (name.charAt(0) == '/') {
|
||||
+ name = name.substring(1);
|
||||
+ }
|
||||
+ if (name.startsWith(packageDirName)) {
|
||||
+ int idx = name.lastIndexOf('/');
|
||||
+ if (idx != -1) {
|
||||
+ packageName = name.substring(0, idx).replace('/', '.');
|
||||
+ }
|
||||
+ if (name.endsWith(".class") && !entry.isDirectory()) {
|
||||
+ String className = name.substring(packageName.length() + 1, name.length() - 6);
|
||||
+ try {
|
||||
+ classes.add(Class.forName(packageName + '.' + className));
|
||||
+ } catch (ClassNotFoundException e) {
|
||||
+ throw new RuntimeException(e);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/CommandBlockConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.experiment;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class CommandBlockConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enable")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.EXPERIMENT;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "force_enable_command_block_execution";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/DisableAsyncCatcherConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.experiment;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class DisableAsyncCatcherConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.EXPERIMENT;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "disable_async_catchers";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/DisableEntityCatchConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.experiment;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class DisableEntityCatchConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.EXPERIMENT;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "disable_entity_exception_catchers";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,31 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/RayTrackingEntityTrackerConfig.java
|
||||
@@ -1,0 +_,28 @@
|
||||
+package me.earthme.luminol.config.modules.experiment;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class RayTrackingEntityTrackerConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+ @ConfigInfo(baseName = "skip_marker_armor_stands")
|
||||
+ public static boolean skipMarkerArmorStands = true;
|
||||
+ @ConfigInfo(baseName = "check_interval_ms")
|
||||
+ public static int checkIntervalMs = 10;
|
||||
+ @ConfigInfo(baseName = "tracing_distance")
|
||||
+ public static int tracingDistance = 48;
|
||||
+ @ConfigInfo(baseName = "hitbox_limit")
|
||||
+ public static int hitboxLimit = 50;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.EXPERIMENT;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "ray_tracking_entity_tracker";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,31 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/FoliaEntityMovingFixConfig.java
|
||||
@@ -1,0 +_,28 @@
|
||||
+package me.earthme.luminol.config.modules.fixes;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class FoliaEntityMovingFixConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled", comments =
|
||||
+ """
|
||||
+ A simple fix of a issue on folia\s
|
||||
+ (Some times the entity would\s
|
||||
+ have a large moment that cross the\s
|
||||
+ different tick regions and it would\s
|
||||
+ make the server crashed) but sometimes it might doesn't work""")
|
||||
+ public static boolean enabled = false;
|
||||
+ @ConfigInfo(baseName = "warn_on_detected")
|
||||
+ public static boolean warnOnDetected = true;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.FIXES;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "folia.fix_high_velocity_issue";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/FoliaOldPositionIssueFixConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.fixes;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class FoliaOldPositionIssueFixConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.FIXES;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "folia.fix_old_position_issue";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,28 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/FoliaPOIAccessOffRegionFixConfig.java
|
||||
@@ -1,0 +_,25 @@
|
||||
+package me.earthme.luminol.config.modules.fixes;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class FoliaPOIAccessOffRegionFixConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled", comments =
|
||||
+ """
|
||||
+ The POIManager of folia has something which has not been patched\s
|
||||
+ for regionized ticking and these would trigger the async catcher\s
|
||||
+ and make the server crash.If you would like to prevent it and didn't\s
|
||||
+ mind the side effect(currently unknown), you can enable this""")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.FIXES;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "folia.fix_poi_access_off_region";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,25 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/UnsafeTeleportationConfig.java
|
||||
@@ -1,0 +_,22 @@
|
||||
+package me.earthme.luminol.config.modules.fixes;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class UnsafeTeleportationConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled", comments = "Allow non player entities enter end portals if enabled.\n" +
|
||||
+ "If you want to use sand duping,please turn on this.\n" +
|
||||
+ "Warning: This would cause some unsafe issues, you could learn more on : https://github.com/PaperMC/Folia/issues/297")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.FIXES;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "allow_unsafe_teleportation";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/VanillaRandomSourceConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.fixes;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class VanillaRandomSourceConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enable_for_player_entity",comments = "Related with RNG cracks")
|
||||
+ public static boolean useLegacyRandomSourceForPlayers = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.FIXES;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "use_vanilla_random_source";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/DisableMovedWronglyThreshold.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class DisableMovedWronglyThreshold implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "disable_moved_wrongly_threshold";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/FoliaWatchogConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class FoliaWatchogConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "tick_region_time_out_ms")
|
||||
+ public static int tickRegionTimeOutMs = 5000;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "folia_watchdog";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/InorderChatConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class InorderChatConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = true;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "mojang_out_of_order_chat_check";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,26 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/KaiijuEntityLimiterConfig.java
|
||||
@@ -1,0 +_,23 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import dev.kaiijumc.kaiiju.KaiijuEntityLimits;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class KaiijuEntityLimiterConfig implements IConfigModule {
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "kaiiju_entity_limiter";
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onLoaded(CommentedFileConfig configInstance) {
|
||||
+ KaiijuEntityLimits.init();
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,53 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/MembarConfig.java
|
||||
@@ -1,0 +_,50 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import me.earthme.luminol.commands.MembarCommand;
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.DoNotLoad;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+import me.earthme.luminol.functions.GlobalServerMemoryBar;
|
||||
+import org.bukkit.Bukkit;
|
||||
+
|
||||
+import java.util.List;
|
||||
+
|
||||
+public class MembarConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean memoryBarEnabled = false;
|
||||
+ @ConfigInfo(baseName = "format")
|
||||
+ public static String memBarFormat = "<gray>Memory usage <yellow>:</yellow> <used>MB<yellow>/</yellow><available>MB";
|
||||
+ @ConfigInfo(baseName = "memory_color_list")
|
||||
+ public static List<String> memColors = List.of("GREEN","YELLOW","RED","PURPLE");
|
||||
+ @ConfigInfo(baseName = "update_interval_ticks")
|
||||
+ public static int updateInterval = 15;
|
||||
+
|
||||
+ @DoNotLoad
|
||||
+ private static boolean inited = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "membar";
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onLoaded(CommentedFileConfig configInstance){
|
||||
+ if (memoryBarEnabled){
|
||||
+ GlobalServerMemoryBar.init();
|
||||
+ }else{
|
||||
+ GlobalServerMemoryBar.cancelBarUpdateTask();
|
||||
+ }
|
||||
+
|
||||
+ if (!inited){
|
||||
+ Bukkit.getCommandMap().register("membar","luminol",new MembarCommand("membar"));
|
||||
+ inited = true;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/OfflineModeWarningConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class OfflineModeWarningConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = true;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "warn_on_offline_mode";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,24 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/PublickeyVerifyConfig.java
|
||||
@@ -1,0 +_,21 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class PublickeyVerifyConfig implements IConfigModule {
|
||||
+
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "verify_publickey_only_in_online_mode";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,53 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/RegionBarConfig.java
|
||||
@@ -1,0 +_,50 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import me.earthme.luminol.commands.RegionBarCommand;
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.DoNotLoad;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+import me.earthme.luminol.functions.GlobalServerRegionBar;
|
||||
+import org.bukkit.Bukkit;
|
||||
+
|
||||
+import java.util.List;
|
||||
+
|
||||
+public class RegionBarConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean regionbarEnabled = false;
|
||||
+ @ConfigInfo(baseName = "format")
|
||||
+ public static String regionBarFormat = "<gray>Util<yellow>:</yellow> <util> Chunks<yellow>:</yellow> <green><chunks></green> Players<yellow>:</yellow> <green><players></green> Entities<yellow>:</yellow> <green><entities></green>";
|
||||
+ @ConfigInfo(baseName = "util_color_list")
|
||||
+ public static List<String> utilColors = List.of("GREEN", "YELLOW", "RED", "PURPLE");
|
||||
+ @ConfigInfo(baseName = "update_interval_ticks")
|
||||
+ public static int updateInterval = 15;
|
||||
+
|
||||
+ @DoNotLoad
|
||||
+ private static boolean inited = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "regionbar";
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onLoaded(CommentedFileConfig configInstance) {
|
||||
+ if (regionbarEnabled) {
|
||||
+ GlobalServerRegionBar.init();
|
||||
+ } else {
|
||||
+ GlobalServerRegionBar.cancelBarUpdateTask();
|
||||
+ }
|
||||
+
|
||||
+ if (!inited) {
|
||||
+ Bukkit.getCommandMap().register("regionbar", "luminol", new RegionBarCommand("regionbar"));
|
||||
+ inited = true;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,62 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/RegionFormatConfig.java
|
||||
@@ -1,0 +_,59 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import abomination.LinearRegionFile;
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import me.earthme.luminol.config.*;
|
||||
+import me.earthme.luminol.utils.EnumRegionFormat;
|
||||
+import net.minecraft.server.MinecraftServer;
|
||||
+
|
||||
+public class RegionFormatConfig implements IConfigModule {
|
||||
+ @HotReloadUnsupported
|
||||
+ @ConfigInfo(baseName = "format")
|
||||
+ public static String format = "MCA";
|
||||
+ @HotReloadUnsupported
|
||||
+ @ConfigInfo(baseName = "linear_compression_level")
|
||||
+ public static int linearCompressionLevel = 1;
|
||||
+ @HotReloadUnsupported
|
||||
+ @ConfigInfo(baseName = "linear_io_thread_count")
|
||||
+ public static int linearIoThreadCount = 6;
|
||||
+ @HotReloadUnsupported
|
||||
+ @ConfigInfo(baseName = "linear_io_flush_delay_ms")
|
||||
+ public static int linearIoFlushDelayMs = 100;
|
||||
+ @HotReloadUnsupported
|
||||
+ @ConfigInfo(baseName = "linear_use_virtual_thread")
|
||||
+ public static boolean linearUseVirtualThread = true;
|
||||
+
|
||||
+ @DoNotLoad
|
||||
+ public static EnumRegionFormat regionFormat;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "region_format";
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onLoaded(CommentedFileConfig configInstance) {
|
||||
+ regionFormat = EnumRegionFormat.fromString(format.toUpperCase());
|
||||
+
|
||||
+ if (regionFormat == null) {
|
||||
+ throw new RuntimeException("Invalid region format: " + format);
|
||||
+ }
|
||||
+
|
||||
+ if (regionFormat == EnumRegionFormat.LINEAR_V2) {
|
||||
+ if (RegionFormatConfig.linearCompressionLevel > 23 || RegionFormatConfig.linearCompressionLevel < 1) {
|
||||
+ MinecraftServer.LOGGER.error("Linear region compression level should be between 1 and 22 in config: {}", RegionFormatConfig.linearCompressionLevel);
|
||||
+ MinecraftServer.LOGGER.error("Falling back to compression level 1.");
|
||||
+ RegionFormatConfig.linearCompressionLevel = 1;
|
||||
+ }
|
||||
+
|
||||
+ LinearRegionFile.SAVE_DELAY_MS = linearIoFlushDelayMs;
|
||||
+ LinearRegionFile.SAVE_THREAD_MAX_COUNT = linearIoThreadCount;
|
||||
+ LinearRegionFile.USE_VIRTUAL_THREAD = linearUseVirtualThread;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,25 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/SecureSeedConfig.java
|
||||
@@ -1,0 +_,22 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class SecureSeedConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled", comments = """
|
||||
+ Once you enable secure seed, all ores and structures are generated with 1024-bit seed
|
||||
+ instead of using 64-bit seed in vanilla, made seed cracker become impossible.""")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "secure_seed";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,50 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/SentryConfig.java
|
||||
@@ -1,0 +_,47 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+import org.apache.logging.log4j.Level;
|
||||
+
|
||||
+public class SentryConfig implements IConfigModule {
|
||||
+
|
||||
+ @ConfigInfo(baseName = "dsn", comments =
|
||||
+ " Sentry DSN for improved error logging, leave blank to disable,\n" +
|
||||
+ " Obtain from https://sentry.io/")
|
||||
+ public static String sentryDsn = "";
|
||||
+
|
||||
+ @ConfigInfo(baseName = "log_level", comments = " Logs with a level higher than or equal to this level will be recorded.")
|
||||
+ public static String logLevel = "WARN";
|
||||
+
|
||||
+ @ConfigInfo(baseName = "only_log_thrown", comments = " Only log with a Throwable will be recorded after enabling this.")
|
||||
+ public static boolean onlyLogThrown = true;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "sentry";
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onLoaded(CommentedFileConfig configInstance) {
|
||||
+ String sentryEnvironment = System.getenv("SENTRY_DSN");
|
||||
+
|
||||
+ sentryDsn = sentryEnvironment != null && !sentryEnvironment.isBlank()
|
||||
+ ? sentryEnvironment
|
||||
+ : configInstance.getOrElse("sentry.dsn", sentryDsn);
|
||||
+
|
||||
+ logLevel = configInstance.getOrElse("sentry.log-level", logLevel);
|
||||
+ onlyLogThrown = configInstance.getOrElse("sentry.only-log-thrown", onlyLogThrown);
|
||||
+
|
||||
+ if (sentryDsn != null && !sentryDsn.isBlank()) {
|
||||
+ gg.pufferfish.pufferfish.sentry.SentryManager.init(Level.getLevel(logLevel));
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,26 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/ServerModNameConfig.java
|
||||
@@ -1,0 +_,23 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class ServerModNameConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "name")
|
||||
+ public static String serverModName = "Luminol";
|
||||
+
|
||||
+ @ConfigInfo(baseName = "vanilla_spoof")
|
||||
+ public static boolean fakeVanilla = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "server_mod_name";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,54 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/TpsBarConfig.java
|
||||
@@ -1,0 +_,51 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import me.earthme.luminol.commands.TpsBarCommand;
|
||||
+import me.earthme.luminol.config.*;
|
||||
+import me.earthme.luminol.functions.GlobalServerTpsBar;
|
||||
+import org.bukkit.Bukkit;
|
||||
+
|
||||
+import java.util.List;
|
||||
+
|
||||
+public class TpsBarConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean tpsbarEnabled = false;
|
||||
+ @ConfigInfo(baseName = "format")
|
||||
+ public static String tpsBarFormat = "<gray>TPS<yellow>:</yellow> <tps> MSPT<yellow>:</yellow> <mspt> Ping<yellow>:</yellow> <ping>ms ChunkHot<yellow>:</yellow> <chunkhot>";
|
||||
+ @ConfigInfo(baseName = "tps_color_list")
|
||||
+ public static List<String> tpsColors = List.of("GREEN","YELLOW","RED","PURPLE");
|
||||
+ @ConfigInfo(baseName = "ping_color_list")
|
||||
+ public static List<String> pingColors = List.of("GREEN","YELLOW","RED","PURPLE");
|
||||
+ @ConfigInfo(baseName = "chunkhot_color_list")
|
||||
+ public static List<String> chunkHotColors = List.of("GREEN","YELLOW","RED","PURPLE");
|
||||
+ @ConfigInfo(baseName = "update_interval_ticks")
|
||||
+ public static int updateInterval = 15;
|
||||
+
|
||||
+ @DoNotLoad
|
||||
+ private static boolean inited = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "tpsbar";
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onLoaded(CommentedFileConfig configInstance){
|
||||
+ if (tpsbarEnabled){
|
||||
+ GlobalServerTpsBar.init();
|
||||
+ }else{
|
||||
+ GlobalServerTpsBar.cancelBarUpdateTask();
|
||||
+ }
|
||||
+
|
||||
+ if (!inited){
|
||||
+ Bukkit.getCommandMap().register("tpsbar","luminol",new TpsBarCommand("tpsbar"));
|
||||
+ inited = true;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/TripwireConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class TripwireConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "tripwire_dupe";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/UsernameCheckConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.misc;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class UsernameCheckConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = true;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.MISC;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "username_checks";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/EntityGoalSelectorInactiveTickConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.optimizations;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class EntityGoalSelectorInactiveTickConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "skip_goal_selector_tick_in_inactive_tick";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/GaleVariableEntityWakeupConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.optimizations;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class GaleVariableEntityWakeupConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "entity_wakeup_duration_ratio_standard_deviation")
|
||||
+ public static double entityWakeUpDurationRatioStandardDeviation = 0.2;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "variable_entity_waking_up";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,27 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/LobotomizeVillageConfig.java
|
||||
@@ -1,0 +_,24 @@
|
||||
+package me.earthme.luminol.config.modules.optimizations;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class LobotomizeVillageConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean villagerLobotomizeEnabled = false;
|
||||
+ @ConfigInfo(baseName = "check_interval")
|
||||
+ public static int villagerLobotomizeCheckInterval = 100;
|
||||
+ @ConfigInfo(baseName = "wait_until_trade_locked")
|
||||
+ public static boolean villagerLobotomizeWaitUntilTradeLocked = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "lobotomize_villager";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,25 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/PetalReduceSensorWorkConfig.java
|
||||
@@ -1,0 +_,22 @@
|
||||
+package me.earthme.luminol.config.modules.optimizations;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class PetalReduceSensorWorkConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = true;
|
||||
+ @ConfigInfo(baseName = "delay_ticks")
|
||||
+ public static int delayTicks = 10;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "reduce_sensor_work";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,25 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/ProjectileChunkReduceConfig.java
|
||||
@@ -1,0 +_,22 @@
|
||||
+package me.earthme.luminol.config.modules.optimizations;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class ProjectileChunkReduceConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "max-loads-per-tick")
|
||||
+ public static int maxProjectileLoadsPerTick;
|
||||
+ @ConfigInfo(baseName = "max-loads-per-projectile")
|
||||
+ public static int maxProjectileLoadsPerProjectile;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "projectile";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/PurpurAlternativeKeepaliveConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.optimizations;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class PurpurAlternativeKeepaliveConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean useAlternateKeepAlive = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "alternative_keepalive_handling";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,56 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/SIMDConfig.java
|
||||
@@ -1,0 +_,53 @@
|
||||
+package me.earthme.luminol.config.modules.optimizations;
|
||||
+
|
||||
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
+import com.mojang.logging.LogUtils;
|
||||
+import gg.pufferfish.pufferfish.simd.SIMDDetection;
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.DoNotLoad;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+import org.slf4j.Logger;
|
||||
+
|
||||
+public class SIMDConfig implements IConfigModule {
|
||||
+ @DoNotLoad
|
||||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = true;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "use_simd";
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onLoaded(CommentedFileConfig configInstance) {
|
||||
+ if (!enabled){
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ // Attempt to detect vectorization
|
||||
+ try {
|
||||
+ SIMDDetection.isEnabled = SIMDDetection.canEnable(LOGGER);
|
||||
+ SIMDDetection.versionLimited = SIMDDetection.getJavaVersion() < 17;
|
||||
+ } catch (NoClassDefFoundError | Exception ignored) {
|
||||
+ ignored.printStackTrace();
|
||||
+ }
|
||||
+
|
||||
+ if (SIMDDetection.isEnabled) {
|
||||
+ LOGGER.info("SIMD operations detected as functional. Will replace some operations with faster versions.");
|
||||
+ } else if (SIMDDetection.versionLimited) {
|
||||
+ LOGGER.warn("Will not enable SIMD! These optimizations are only safely supported on Java 17+.");
|
||||
+ } else {
|
||||
+ LOGGER.warn("SIMD operations are available for your server, but are not configured!");
|
||||
+ LOGGER.warn("To enable additional optimizations, add \"--add-modules=jdk.incubator.vector\" to your startup flags, BEFORE the \"-jar\".");
|
||||
+ LOGGER.warn("If you have already added this flag, then SIMD operations are not supported on your JVM or CPU.");
|
||||
+ LOGGER.warn("Debug: Java: {}, test run: {}", System.getProperty("java.version"), SIMDDetection.testRun);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,23 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/SuffocationOptimizationConfig.java
|
||||
@@ -1,0 +_,20 @@
|
||||
+package me.earthme.luminol.config.modules.optimizations;
|
||||
+
|
||||
+import me.earthme.luminol.config.ConfigInfo;
|
||||
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||
+import me.earthme.luminol.config.IConfigModule;
|
||||
+
|
||||
+public class SuffocationOptimizationConfig implements IConfigModule {
|
||||
+ @ConfigInfo(baseName = "enabled")
|
||||
+ public static boolean enabled = false;
|
||||
+
|
||||
+ @Override
|
||||
+ public EnumConfigCategory getCategory() {
|
||||
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public String getBaseName() {
|
||||
+ return "suffocation_optimization";
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,174 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/functions/GlobalServerMemoryBar.java
|
||||
@@ -1,0 +_,171 @@
|
||||
+package me.earthme.luminol.functions;
|
||||
+
|
||||
+import com.google.common.collect.Maps;
|
||||
+import com.mojang.logging.LogUtils;
|
||||
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||
+import me.earthme.luminol.config.modules.misc.MembarConfig;
|
||||
+import me.earthme.luminol.utils.NullPlugin;
|
||||
+import net.kyori.adventure.bossbar.BossBar;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
+import org.bukkit.entity.Player;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+import org.slf4j.Logger;
|
||||
+
|
||||
+import java.lang.management.ManagementFactory;
|
||||
+import java.lang.management.MemoryUsage;
|
||||
+import java.util.*;
|
||||
+
|
||||
+public class GlobalServerMemoryBar {
|
||||
+ protected static final NullPlugin NULL_PLUGIN = new NullPlugin();
|
||||
+ protected static final Map<UUID, BossBar> uuid2Bossbars = Maps.newConcurrentMap();
|
||||
+ protected static final Map<UUID, ScheduledTask> scheduledTasks = new HashMap<>();
|
||||
+ protected static volatile ScheduledTask scannerTask = null;
|
||||
+ private static final Logger logger = LogUtils.getLogger();
|
||||
+
|
||||
+ public static void init(){
|
||||
+ cancelBarUpdateTask();
|
||||
+
|
||||
+ scannerTask = Bukkit.getGlobalRegionScheduler().runAtFixedRate(NULL_PLUGIN, unused -> {
|
||||
+ try {
|
||||
+ update();
|
||||
+ }catch (Exception e){
|
||||
+ logger.error(e.getLocalizedMessage());
|
||||
+ }
|
||||
+ }, 1, MembarConfig.updateInterval);
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ public static void cancelBarUpdateTask(){
|
||||
+ if (scannerTask == null || scannerTask.isCancelled()){
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ scannerTask.cancel();
|
||||
+
|
||||
+ for (ScheduledTask task : scheduledTasks.values()) {
|
||||
+ if (!task.isCancelled()) {
|
||||
+ task.cancel();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static boolean isPlayerVisible(Player player){
|
||||
+ return ((CraftPlayer) player).getHandle().isMemBarVisible;
|
||||
+ }
|
||||
+
|
||||
+ public static void setVisibilityForPlayer(Player target,boolean canSee){
|
||||
+ ((CraftPlayer) target).getHandle().isMemBarVisible = canSee;
|
||||
+ }
|
||||
+
|
||||
+ private static void update(){
|
||||
+ doUpdate();
|
||||
+ cleanUp();
|
||||
+ }
|
||||
+
|
||||
+ private static void cleanUp(){
|
||||
+ final List<UUID> toCleanUp = new ArrayList<>();
|
||||
+
|
||||
+ for (Map.Entry<UUID, ScheduledTask> toCheck : scheduledTasks.entrySet()) {
|
||||
+ if (toCheck.getValue().isCancelled()) {
|
||||
+ toCleanUp.add(toCheck.getKey());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (UUID uuid : toCleanUp) {
|
||||
+ scheduledTasks.remove(uuid);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void doUpdate(){
|
||||
+ for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
+ scheduledTasks.computeIfAbsent(player.getUniqueId(), unused -> createBossBarForPlayer(player));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static ScheduledTask createBossBarForPlayer(Player apiPlayer) {
|
||||
+ return apiPlayer.getScheduler().runAtFixedRate(NULL_PLUGIN, (unused) -> {
|
||||
+ final UUID playerUUID = apiPlayer.getUniqueId();
|
||||
+
|
||||
+ if (!isPlayerVisible(apiPlayer)) {
|
||||
+ final BossBar removed = uuid2Bossbars.remove(playerUUID);
|
||||
+
|
||||
+ if (removed != null) {
|
||||
+ apiPlayer.hideBossBar(removed);
|
||||
+ }
|
||||
+
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
|
||||
+
|
||||
+ long used = heap.getUsed();
|
||||
+ long xmx = heap.getMax();
|
||||
+
|
||||
+ BossBar targetBossbar = uuid2Bossbars.computeIfAbsent(
|
||||
+ playerUUID,
|
||||
+ (unused1) -> BossBar.bossBar(Component.text(""),0.0F, BossBar.Color.valueOf(MembarConfig.memColors.get(3)), BossBar.Overlay.NOTCHED_20)
|
||||
+ );
|
||||
+
|
||||
+ apiPlayer.showBossBar(targetBossbar);
|
||||
+
|
||||
+ updateMembar(targetBossbar, used, xmx);
|
||||
+ }, () -> {
|
||||
+ final BossBar removed = uuid2Bossbars.remove(apiPlayer.getUniqueId());
|
||||
+
|
||||
+ if (removed != null) {
|
||||
+ apiPlayer.hideBossBar(removed);
|
||||
+ }
|
||||
+ }, 1, MembarConfig.updateInterval);
|
||||
+ }
|
||||
+
|
||||
+ private static void updateMembar(@NotNull BossBar bar, long used, long xmx){
|
||||
+ double percent = Math.max(Math.min((float) used / xmx, 1.0F), 0.0F);
|
||||
+ bar.name(MiniMessage.miniMessage().deserialize(
|
||||
+ MembarConfig.memBarFormat,
|
||||
+ Placeholder.component("used", getMemoryComponent(used,xmx)),
|
||||
+ Placeholder.component("available",getMaxMemComponent(xmx))
|
||||
+ ));
|
||||
+ bar.color(barColorFromMemory(percent));
|
||||
+ bar.progress((float) percent);
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getMaxMemComponent(double max){
|
||||
+ final BossBar.Color colorBukkit = BossBar.Color.GREEN;
|
||||
+ final String colorString = colorBukkit.name();
|
||||
+
|
||||
+ final String content = "<%s><text></%s>";
|
||||
+ final String replaced = String.format(content,colorString,colorString);
|
||||
+
|
||||
+ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.format("%.2f", max / (1024 * 1024))));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getMemoryComponent(long used,long max){
|
||||
+ final BossBar.Color colorBukkit = barColorFromMemory(Math.max(Math.min((float) used / max, 1.0F), 0.0F));
|
||||
+ final String colorString = colorBukkit.name();
|
||||
+
|
||||
+ final String content = "<%s><text></%s>";
|
||||
+ final String replaced = String.format(content,colorString,colorString);
|
||||
+
|
||||
+ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.format("%.2f", (double)used / (1024 * 1024))));
|
||||
+ }
|
||||
+
|
||||
+ private static BossBar.Color barColorFromMemory(double memPercent){
|
||||
+ if (memPercent == -1){
|
||||
+ return BossBar.Color.valueOf(MembarConfig.memColors.get(3));
|
||||
+ }
|
||||
+
|
||||
+ if (memPercent <= 50){
|
||||
+ return BossBar.Color.valueOf(MembarConfig.memColors.getFirst());
|
||||
+ }
|
||||
+
|
||||
+ if (memPercent <= 70){
|
||||
+ return BossBar.Color.valueOf(MembarConfig.memColors.get(1));
|
||||
+ }
|
||||
+
|
||||
+ return BossBar.Color.valueOf(MembarConfig.memColors.get(2));
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,187 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/functions/GlobalServerRegionBar.java
|
||||
@@ -1,0 +_,184 @@
|
||||
+package me.earthme.luminol.functions;
|
||||
+
|
||||
+import com.google.common.collect.Maps;
|
||||
+import com.mojang.logging.LogUtils;
|
||||
+import io.papermc.paper.threadedregions.*;
|
||||
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||
+import me.earthme.luminol.config.modules.misc.RegionBarConfig;
|
||||
+import me.earthme.luminol.utils.NullPlugin;
|
||||
+import net.kyori.adventure.bossbar.BossBar;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
+import org.bukkit.entity.Player;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+import org.slf4j.Logger;
|
||||
+
|
||||
+import java.text.DecimalFormat;
|
||||
+import java.util.*;
|
||||
+
|
||||
+public class GlobalServerRegionBar {
|
||||
+ protected static final NullPlugin NULL_PLUGIN = new NullPlugin();
|
||||
+ protected static final Map<UUID, BossBar> uuid2Bossbars = Maps.newConcurrentMap();
|
||||
+ protected static final Map<UUID, ScheduledTask> scheduledTasks = new HashMap<>();
|
||||
+
|
||||
+ protected static volatile ScheduledTask scannerTask = null;
|
||||
+ private static final Logger logger = LogUtils.getLogger();
|
||||
+
|
||||
+ private static final ThreadLocal<DecimalFormat> ONE_DECIMAL_PLACES = ThreadLocal.withInitial(() -> new DecimalFormat("#,##0.0"));
|
||||
+
|
||||
+ public static void init() {
|
||||
+ cancelBarUpdateTask();
|
||||
+
|
||||
+ scannerTask = Bukkit.getGlobalRegionScheduler().runAtFixedRate(NULL_PLUGIN, unused -> {
|
||||
+ try {
|
||||
+ update();
|
||||
+ cleanUp();
|
||||
+ } catch (Exception e) {
|
||||
+ logger.error(e.getLocalizedMessage());
|
||||
+ }
|
||||
+ }, 1, RegionBarConfig.updateInterval);
|
||||
+ }
|
||||
+
|
||||
+ public static void cancelBarUpdateTask() {
|
||||
+ if (scannerTask == null || scannerTask.isCancelled()) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ scannerTask.cancel();
|
||||
+
|
||||
+ for (ScheduledTask task : scheduledTasks.values()) {
|
||||
+ if (!task.isCancelled()) {
|
||||
+ task.cancel();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static boolean isPlayerVisible(Player player) {
|
||||
+ return ((CraftPlayer) player).getHandle().isRegionBarVisible;
|
||||
+ }
|
||||
+
|
||||
+ public static void setVisibilityForPlayer(Player target, boolean canSee) {
|
||||
+ ((CraftPlayer) target).getHandle().isRegionBarVisible = canSee;
|
||||
+ }
|
||||
+
|
||||
+ private static void update() {
|
||||
+ for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
+ scheduledTasks.computeIfAbsent(player.getUniqueId(), unused -> createBossBarForPlayer(player));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void cleanUp() {
|
||||
+ final List<UUID> toCleanUp = new ArrayList<>();
|
||||
+
|
||||
+ for (Map.Entry<UUID, ScheduledTask> toCheck : scheduledTasks.entrySet()) {
|
||||
+ if (toCheck.getValue().isCancelled()) {
|
||||
+ toCleanUp.add(toCheck.getKey());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (UUID uuid : toCleanUp) {
|
||||
+ scheduledTasks.remove(uuid);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static ScheduledTask createBossBarForPlayer(@NotNull Player apiPlayer) {
|
||||
+ final UUID playerUUID = apiPlayer.getUniqueId();
|
||||
+
|
||||
+ return apiPlayer.getScheduler().runAtFixedRate(NULL_PLUGIN, (n) -> {
|
||||
+ if (!isPlayerVisible(apiPlayer)) {
|
||||
+ final BossBar removed = uuid2Bossbars.remove(playerUUID);
|
||||
+
|
||||
+ if (removed != null) {
|
||||
+ apiPlayer.hideBossBar(removed);
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region = TickRegionScheduler.getCurrentRegion();
|
||||
+ final TickData.TickReportData reportData = region.getData().getRegionSchedulingHandle().getTickReport5s(System.nanoTime());
|
||||
+ final TickRegions.RegionStats regionStats = region.getData().getRegionStats();
|
||||
+
|
||||
+ BossBar targetBossbar = uuid2Bossbars.computeIfAbsent(
|
||||
+ playerUUID,
|
||||
+ unused -> BossBar.bossBar(Component.text(""), 0.0F, BossBar.Color.GREEN, BossBar.Overlay.NOTCHED_20)
|
||||
+ );
|
||||
+
|
||||
+ apiPlayer.showBossBar(targetBossbar);
|
||||
+
|
||||
+ if (reportData != null) {
|
||||
+ final double utilisation = reportData.utilisation();
|
||||
+ final int chunkCount = regionStats.getChunkCount();
|
||||
+ final int playerCount = regionStats.getPlayerCount();
|
||||
+ final int entityCount = regionStats.getEntityCount();
|
||||
+
|
||||
+ updateRegionBar(utilisation, chunkCount, playerCount, entityCount, targetBossbar);
|
||||
+ }
|
||||
+ }, () -> {
|
||||
+ final BossBar removed = uuid2Bossbars.remove(playerUUID); // Auto clean up it
|
||||
+
|
||||
+ if (removed != null) {
|
||||
+ apiPlayer.hideBossBar(removed);
|
||||
+ }
|
||||
+ }, 1, RegionBarConfig.updateInterval);
|
||||
+ }
|
||||
+
|
||||
+ private static void updateRegionBar(double utilisation, int chunks, int players, int entities, @NotNull BossBar bar) {
|
||||
+ final double utilisationPercent = utilisation * 100.0;
|
||||
+ final String formattedUtil = ONE_DECIMAL_PLACES.get().format(utilisationPercent);
|
||||
+
|
||||
+ bar.name(MiniMessage.miniMessage().deserialize(
|
||||
+ RegionBarConfig.regionBarFormat,
|
||||
+ Placeholder.component("util", getUtilComponent(formattedUtil)),
|
||||
+ Placeholder.component("chunks", getChunksComponent(chunks)),
|
||||
+ Placeholder.component("players", getPlayersComponent(players)),
|
||||
+ Placeholder.component("entities", getEntitiesComponent(entities))
|
||||
+ ));
|
||||
+
|
||||
+ bar.color(barColorFromUtil(utilisationPercent));
|
||||
+ bar.progress((float) Math.min(1.0, Math.max(utilisation, 0)));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getEntitiesComponent(int entities) {
|
||||
+ final String content = "<text>";
|
||||
+ return MiniMessage.miniMessage().deserialize(content, Placeholder.parsed("text", String.valueOf(entities)));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getPlayersComponent(int players) {
|
||||
+ final String content = "<text>";
|
||||
+ return MiniMessage.miniMessage().deserialize(content, Placeholder.parsed("text", String.valueOf(players)));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getChunksComponent(int chunks) {
|
||||
+ final String content = "<text>";
|
||||
+ return MiniMessage.miniMessage().deserialize(content, Placeholder.parsed("text", String.valueOf(chunks)));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getUtilComponent(String formattedUtil) {
|
||||
+ final BossBar.Color colorBukkit = barColorFromUtil(Double.parseDouble(formattedUtil));
|
||||
+ final String colorString = colorBukkit.name();
|
||||
+
|
||||
+ final String content = "<%s><text></%s>";
|
||||
+ final String replaced = String.format(content, colorString, colorString);
|
||||
+
|
||||
+ return MiniMessage.miniMessage().deserialize(replaced, Placeholder.parsed("text", formattedUtil + "%"));
|
||||
+ }
|
||||
+
|
||||
+ private static BossBar.Color barColorFromUtil(double util) {
|
||||
+ if (util >= 100) {
|
||||
+ return BossBar.Color.valueOf(RegionBarConfig.utilColors.get(3)); // PURPLE
|
||||
+ }
|
||||
+
|
||||
+ if (util >= 70) {
|
||||
+ return BossBar.Color.valueOf(RegionBarConfig.utilColors.get(2)); // RED
|
||||
+ }
|
||||
+
|
||||
+ if (util >= 50) {
|
||||
+ return BossBar.Color.valueOf(RegionBarConfig.utilColors.get(1)); // YELLOW
|
||||
+ }
|
||||
+
|
||||
+ return BossBar.Color.valueOf(RegionBarConfig.utilColors.get(0)); // GREEN
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,244 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/functions/GlobalServerTpsBar.java
|
||||
@@ -1,0 +_,241 @@
|
||||
+package me.earthme.luminol.functions;
|
||||
+
|
||||
+import com.google.common.collect.Maps;
|
||||
+import com.mojang.logging.LogUtils;
|
||||
+import io.papermc.paper.threadedregions.ThreadedRegionizer;
|
||||
+import io.papermc.paper.threadedregions.TickData;
|
||||
+import io.papermc.paper.threadedregions.TickRegionScheduler;
|
||||
+import io.papermc.paper.threadedregions.TickRegions;
|
||||
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
|
||||
+import me.earthme.luminol.config.modules.misc.TpsBarConfig;
|
||||
+import me.earthme.luminol.utils.NullPlugin;
|
||||
+import net.kyori.adventure.bossbar.BossBar;
|
||||
+import net.kyori.adventure.text.Component;
|
||||
+import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
+import org.bukkit.Bukkit;
|
||||
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
+import org.bukkit.entity.Player;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+import org.slf4j.Logger;
|
||||
+
|
||||
+import java.util.*;
|
||||
+
|
||||
+public class GlobalServerTpsBar {
|
||||
+ protected static final NullPlugin NULL_PLUGIN = new NullPlugin();
|
||||
+ protected static final Map<UUID, BossBar> uuid2Bossbars = Maps.newConcurrentMap();
|
||||
+ protected static final Map<UUID, ScheduledTask> scheduledTasks = new HashMap<>();
|
||||
+
|
||||
+ protected static volatile ScheduledTask scannerTask = null;
|
||||
+ private static final Logger logger = LogUtils.getLogger();
|
||||
+
|
||||
+ public static void init(){
|
||||
+ cancelBarUpdateTask();
|
||||
+
|
||||
+ scannerTask = Bukkit.getGlobalRegionScheduler().runAtFixedRate(NULL_PLUGIN, unused -> {
|
||||
+ try {
|
||||
+ update();
|
||||
+ cleanUp();
|
||||
+ }catch (Exception e){
|
||||
+ logger.error(e.getLocalizedMessage());
|
||||
+ }
|
||||
+ }, 1, TpsBarConfig.updateInterval);
|
||||
+ }
|
||||
+
|
||||
+ public static void cancelBarUpdateTask(){
|
||||
+ if (scannerTask == null || scannerTask.isCancelled()){
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ scannerTask.cancel();
|
||||
+
|
||||
+ for (ScheduledTask task : scheduledTasks.values()) {
|
||||
+ if (!task.isCancelled()) {
|
||||
+ task.cancel();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static boolean isPlayerVisible(Player player){
|
||||
+ return ((CraftPlayer) player).getHandle().isTpsBarVisible;
|
||||
+ }
|
||||
+
|
||||
+ public static void setVisibilityForPlayer(Player target,boolean canSee){
|
||||
+ ((CraftPlayer) target).getHandle().isTpsBarVisible = canSee;
|
||||
+ }
|
||||
+
|
||||
+ private static void update(){
|
||||
+ for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
+ scheduledTasks.computeIfAbsent(player.getUniqueId(), unused -> createBossBarForPlayer(player));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void cleanUp() {
|
||||
+ final List<UUID> toCleanUp = new ArrayList<>();
|
||||
+
|
||||
+ for (Map.Entry<UUID, ScheduledTask> toCheck : scheduledTasks.entrySet()) {
|
||||
+ if (toCheck.getValue().isCancelled()) {
|
||||
+ toCleanUp.add(toCheck.getKey());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (UUID uuid : toCleanUp) {
|
||||
+ scheduledTasks.remove(uuid);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public static ScheduledTask createBossBarForPlayer(@NotNull Player apiPlayer) {
|
||||
+ final UUID playerUUID = apiPlayer.getUniqueId();
|
||||
+
|
||||
+ return apiPlayer.getScheduler().runAtFixedRate(NULL_PLUGIN, (n) -> {
|
||||
+ if (!isPlayerVisible(apiPlayer)) {
|
||||
+ final BossBar removed = uuid2Bossbars.remove(playerUUID);
|
||||
+
|
||||
+ if (removed != null) {
|
||||
+ apiPlayer.hideBossBar(removed);
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> region = TickRegionScheduler.getCurrentRegion();
|
||||
+ final TickData.TickReportData reportData = region.getData().getRegionSchedulingHandle().getTickReport5s(System.nanoTime());
|
||||
+
|
||||
+
|
||||
+ BossBar targetBossbar = uuid2Bossbars.computeIfAbsent(
|
||||
+ playerUUID,
|
||||
+ unused -> BossBar.bossBar(Component.text(""),0.0F, BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(3)), BossBar.Overlay.NOTCHED_20)
|
||||
+ );
|
||||
+
|
||||
+ apiPlayer.showBossBar(targetBossbar);
|
||||
+
|
||||
+ if (reportData != null){
|
||||
+ final TickData.SegmentData tpsData = reportData.tpsData().segmentAll();
|
||||
+ final double mspt = reportData.timePerTickData().segmentAll().average() / 1.0E6;
|
||||
+
|
||||
+ updateTpsBar(tpsData.average(), mspt, targetBossbar, apiPlayer);
|
||||
+ }
|
||||
+ }, () -> {
|
||||
+ final BossBar removed = uuid2Bossbars.remove(playerUUID); // Auto clean up it
|
||||
+
|
||||
+ if (removed != null) {
|
||||
+ apiPlayer.hideBossBar(removed);
|
||||
+ }
|
||||
+ }, 1, TpsBarConfig.updateInterval);
|
||||
+ }
|
||||
+
|
||||
+ private static void updateTpsBar(double tps, double mspt, @NotNull BossBar bar, @NotNull Player player){
|
||||
+ bar.name(MiniMessage.miniMessage().deserialize(
|
||||
+ TpsBarConfig.tpsBarFormat,
|
||||
+ Placeholder.component("tps",getTpsComponent(tps)),
|
||||
+ Placeholder.component("mspt",getMsptComponent(mspt)),
|
||||
+ Placeholder.component("ping",getPingComponent(player.getPing())),
|
||||
+ Placeholder.component("chunkhot",getChunkHotComponent(player.getNearbyChunkHot()))
|
||||
+ ));
|
||||
+ bar.color(barColorFromTps(tps));
|
||||
+ bar.progress((float) Math.min((float)1,Math.max(mspt / 50,0)));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getPingComponent(int ping){
|
||||
+ final BossBar.Color colorBukkit = barColorFromPing(ping);
|
||||
+ final String colorString = colorBukkit.name();
|
||||
+
|
||||
+ final String content = "<%s><text></%s>";
|
||||
+ final String replaced = String.format(content,colorString,colorString);
|
||||
+
|
||||
+ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.valueOf(ping)));
|
||||
+ }
|
||||
+
|
||||
+ private static BossBar.Color barColorFromPing(int ping){
|
||||
+ if (ping == -1){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.pingColors.get(3));
|
||||
+ }
|
||||
+
|
||||
+ if (ping <= 80){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.pingColors.get(0));
|
||||
+ }
|
||||
+
|
||||
+ if (ping <= 160){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.pingColors.get(1));
|
||||
+ }
|
||||
+
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.pingColors.get(2));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getMsptComponent(double mspt){
|
||||
+ final BossBar.Color colorBukkit = barColorFromMspt(mspt);
|
||||
+ final String colorString = colorBukkit.name();
|
||||
+
|
||||
+ final String content = "<%s><text></%s>";
|
||||
+ final String replaced = String.format(content,colorString,colorString);
|
||||
+
|
||||
+ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.format("%.2f", mspt)));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getChunkHotComponent(long chunkHot){
|
||||
+ final BossBar.Color colorBukkit = barColorFromChunkHot(chunkHot);
|
||||
+ final String colorString = colorBukkit.name();
|
||||
+
|
||||
+ final String content = "<%s><text></%s>";
|
||||
+ final String replaced = String.format(content,colorString,colorString);
|
||||
+
|
||||
+ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.valueOf(chunkHot)));
|
||||
+ }
|
||||
+
|
||||
+ private static BossBar.Color barColorFromChunkHot(long chunkHot){
|
||||
+ if (chunkHot == -1){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.chunkHotColors.get(3));
|
||||
+ }
|
||||
+
|
||||
+ if (chunkHot <= 300000L){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.chunkHotColors.get(0));
|
||||
+ }
|
||||
+
|
||||
+ if (chunkHot <= 500000L){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.chunkHotColors.get(1));
|
||||
+ }
|
||||
+
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.chunkHotColors.get(2));
|
||||
+ }
|
||||
+
|
||||
+ private static BossBar.Color barColorFromMspt(double mspt){
|
||||
+ if (mspt == -1){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(3));
|
||||
+ }
|
||||
+
|
||||
+ if (mspt <= 25){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(0));
|
||||
+ }
|
||||
+
|
||||
+ if (mspt <= 50){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(1));
|
||||
+ }
|
||||
+
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(2));
|
||||
+ }
|
||||
+
|
||||
+ private static @NotNull Component getTpsComponent(double tps){
|
||||
+ final BossBar.Color colorBukkit = barColorFromTps(tps);
|
||||
+ final String colorString = colorBukkit.name();
|
||||
+
|
||||
+ final String content = "<%s><text></%s>";
|
||||
+ final String replaced = String.format(content,colorString,colorString);
|
||||
+
|
||||
+ return MiniMessage.miniMessage().deserialize(replaced,Placeholder.parsed("text", String.format("%.2f", tps)));
|
||||
+ }
|
||||
+
|
||||
+ private static BossBar.Color barColorFromTps(double tps){
|
||||
+ if (tps == -1){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(3));
|
||||
+ }
|
||||
+
|
||||
+ if (tps >= 18){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(0));
|
||||
+ }
|
||||
+
|
||||
+ if (tps >= 15){
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(1));
|
||||
+ }
|
||||
+
|
||||
+ return BossBar.Color.valueOf(TpsBarConfig.tpsColors.get(2));
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,43 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/utils/EnumRegionFormat.java
|
||||
@@ -1,0 +_,40 @@
|
||||
+package me.earthme.luminol.utils;
|
||||
+
|
||||
+import abomination.LinearRegionFile;
|
||||
+import me.earthme.luminol.config.modules.misc.RegionFormatConfig;
|
||||
+import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
+import org.jetbrains.annotations.Nullable;
|
||||
+
|
||||
+public enum EnumRegionFormat {
|
||||
+ MCA("mca", "mca" , (info) -> new RegionFile(info.info(), info.filePath(), info.folder(), info.sync())),
|
||||
+ LINEAR_V2("linear_v2", "linear" ,(info) -> new LinearRegionFile(info.info(), info.filePath(), info.folder(), info.sync(), RegionFormatConfig.linearCompressionLevel));
|
||||
+
|
||||
+ private final String name;
|
||||
+ private final String argument;
|
||||
+ private final IRegionCreateFunction creator;
|
||||
+
|
||||
+ EnumRegionFormat(String name, String argument, IRegionCreateFunction creator) {
|
||||
+ this.name = name;
|
||||
+ this.argument = argument;
|
||||
+ this.creator = creator;
|
||||
+ }
|
||||
+
|
||||
+ @Nullable
|
||||
+ public static EnumRegionFormat fromString(String string) {
|
||||
+ for (EnumRegionFormat format : values()) {
|
||||
+ if (format.name.equalsIgnoreCase(string)) {
|
||||
+ return format;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ public IRegionCreateFunction getCreator() {
|
||||
+ return this.creator;
|
||||
+ }
|
||||
+
|
||||
+ public String getArgument() {
|
||||
+ return this.argument;
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,12 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/utils/IRegionCreateFunction.java
|
||||
@@ -1,0 +_,9 @@
|
||||
+package me.earthme.luminol.utils;
|
||||
+
|
||||
+import abomination.IRegionFile;
|
||||
+
|
||||
+import java.io.IOException;
|
||||
+
|
||||
+public interface IRegionCreateFunction {
|
||||
+ IRegionFile create(RegionCreatorInfo info) throws IOException;
|
||||
+}
|
||||
@@ -0,0 +1,155 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/utils/NullPlugin.java
|
||||
@@ -1,0 +_,152 @@
|
||||
+package me.earthme.luminol.utils;
|
||||
+
|
||||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
|
||||
+import org.bukkit.Server;
|
||||
+import org.bukkit.command.Command;
|
||||
+import org.bukkit.command.CommandSender;
|
||||
+import org.bukkit.configuration.file.FileConfiguration;
|
||||
+import org.bukkit.generator.BiomeProvider;
|
||||
+import org.bukkit.generator.ChunkGenerator;
|
||||
+import org.bukkit.plugin.*;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+import org.jetbrains.annotations.Nullable;
|
||||
+
|
||||
+import java.io.File;
|
||||
+import java.io.InputStream;
|
||||
+import java.util.List;
|
||||
+import java.util.logging.Logger;
|
||||
+
|
||||
+public class NullPlugin extends PluginBase {
|
||||
+ private boolean enabled = true;
|
||||
+
|
||||
+ private final String pluginName;
|
||||
+ private PluginDescriptionFile pdf;
|
||||
+
|
||||
+ public NullPlugin() {
|
||||
+ this.pluginName = "Minecraft";
|
||||
+ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms");
|
||||
+ }
|
||||
+
|
||||
+ public void setEnabled(boolean enabled) {
|
||||
+ this.enabled = enabled;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public File getDataFolder() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public PluginDescriptionFile getDescription() {
|
||||
+ return pdf;
|
||||
+ }
|
||||
+ // Paper start
|
||||
+ @Override
|
||||
+ public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
|
||||
+ return pdf;
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
+ @Override
|
||||
+ public FileConfiguration getConfig() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public InputStream getResource(String filename) {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void saveConfig() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void saveDefaultConfig() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void saveResource(String resourcePath, boolean replace) {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void reloadConfig() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public PluginLogger getLogger() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public PluginLoader getPluginLoader() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public Server getServer() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isEnabled() {
|
||||
+ return enabled;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onDisable() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onLoad() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void onEnable() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isNaggable() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setNaggable(boolean canNag) {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+
|
||||
+ // Paper start - lifecycle events
|
||||
+ @Override
|
||||
+ public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<org.bukkit.plugin.Plugin> getLifecycleManager() {
|
||||
+ throw new UnsupportedOperationException("Not supported.");
|
||||
+ }
|
||||
+ // Paper end - lifecycle events
|
||||
+}
|
||||
@@ -0,0 +1,10 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/me/earthme/luminol/utils/RegionCreatorInfo.java
|
||||
@@ -1,0 +_,7 @@
|
||||
+package me.earthme.luminol.utils;
|
||||
+
|
||||
+import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
|
||||
+
|
||||
+import java.nio.file.Path;
|
||||
+
|
||||
+public record RegionCreatorInfo (RegionStorageInfo info, Path filePath, Path folder, boolean sync) {}
|
||||
@@ -0,0 +1,97 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/su/plo/matter/Globals.java
|
||||
@@ -1,0 +_,94 @@
|
||||
+package su.plo.matter;
|
||||
+
|
||||
+import com.google.common.collect.Iterables;
|
||||
+import net.minecraft.server.level.ServerLevel;
|
||||
+
|
||||
+import java.math.BigInteger;
|
||||
+import java.security.SecureRandom;
|
||||
+import java.util.Optional;
|
||||
+
|
||||
+public class Globals {
|
||||
+ public static final int WORLD_SEED_LONGS = 16;
|
||||
+ public static final int WORLD_SEED_BITS = WORLD_SEED_LONGS * 64;
|
||||
+
|
||||
+ public static final long[] worldSeed = new long[WORLD_SEED_LONGS];
|
||||
+ public static final ThreadLocal<Integer> dimension = ThreadLocal.withInitial(() -> 0);
|
||||
+
|
||||
+ public enum Salt {
|
||||
+ UNDEFINED,
|
||||
+ BASTION_FEATURE,
|
||||
+ WOODLAND_MANSION_FEATURE,
|
||||
+ MINESHAFT_FEATURE,
|
||||
+ BURIED_TREASURE_FEATURE,
|
||||
+ NETHER_FORTRESS_FEATURE,
|
||||
+ PILLAGER_OUTPOST_FEATURE,
|
||||
+ GEODE_FEATURE,
|
||||
+ NETHER_FOSSIL_FEATURE,
|
||||
+ OCEAN_MONUMENT_FEATURE,
|
||||
+ RUINED_PORTAL_FEATURE,
|
||||
+ POTENTIONAL_FEATURE,
|
||||
+ GENERATE_FEATURE,
|
||||
+ JIGSAW_PLACEMENT,
|
||||
+ STRONGHOLDS,
|
||||
+ POPULATION,
|
||||
+ DECORATION,
|
||||
+ SLIME_CHUNK
|
||||
+ }
|
||||
+
|
||||
+ public static void setupGlobals(ServerLevel world) {
|
||||
+ if (!me.earthme.luminol.config.modules.misc.SecureSeedConfig.enabled) return;
|
||||
+
|
||||
+ long[] seed = world.getServer().getWorldData().worldGenOptions().featureSeed();
|
||||
+ System.arraycopy(seed, 0, worldSeed, 0, WORLD_SEED_LONGS);
|
||||
+ int worldIndex = Iterables.indexOf(world.getServer().levelKeys(), it -> it == world.dimension());
|
||||
+ if (worldIndex == -1)
|
||||
+ worldIndex = world.getServer().levelKeys().size(); // if we are in world construction it may not have been added to the map yet
|
||||
+ dimension.set(worldIndex);
|
||||
+ }
|
||||
+
|
||||
+ public static long[] createRandomWorldSeed() {
|
||||
+ long[] seed = new long[WORLD_SEED_LONGS];
|
||||
+ SecureRandom rand = new SecureRandom();
|
||||
+ for (int i = 0; i < WORLD_SEED_LONGS; i++) {
|
||||
+ seed[i] = rand.nextLong();
|
||||
+ }
|
||||
+ return seed;
|
||||
+ }
|
||||
+
|
||||
+ // 1024-bit string -> 16 * 64 long[]
|
||||
+ public static Optional<long[]> parseSeed(String seedStr) {
|
||||
+ if (seedStr.isEmpty()) return Optional.empty();
|
||||
+
|
||||
+ if (seedStr.length() != WORLD_SEED_BITS) {
|
||||
+ throw new IllegalArgumentException("Secure seed length must be " + WORLD_SEED_BITS + "-bit but found " + seedStr.length() + "-bit.");
|
||||
+ }
|
||||
+
|
||||
+ long[] seed = new long[WORLD_SEED_LONGS];
|
||||
+
|
||||
+ for (int i = 0; i < WORLD_SEED_LONGS; i++) {
|
||||
+ int start = i * 64;
|
||||
+ int end = start + 64;
|
||||
+ String seedSection = seedStr.substring(start, end);
|
||||
+
|
||||
+ BigInteger seedInDecimal = new BigInteger(seedSection, 2);
|
||||
+ seed[i] = seedInDecimal.longValue();
|
||||
+ }
|
||||
+
|
||||
+ return Optional.of(seed);
|
||||
+ }
|
||||
+
|
||||
+ // 16 * 64 long[] -> 1024-bit string
|
||||
+ public static String seedToString(long[] seed) {
|
||||
+ StringBuilder sb = new StringBuilder();
|
||||
+
|
||||
+ for (long longV : seed) {
|
||||
+ // Convert to 64-bit binary string per long
|
||||
+ // Use format to keep 64-bit length, and use 0 to complete space
|
||||
+ String binaryStr = String.format("%64s", Long.toBinaryString(longV)).replace(' ', '0');
|
||||
+
|
||||
+ sb.append(binaryStr);
|
||||
+ }
|
||||
+
|
||||
+ return sb.toString();
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,76 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/su/plo/matter/Hashing.java
|
||||
@@ -1,0 +_,73 @@
|
||||
+package su.plo.matter;
|
||||
+
|
||||
+public class Hashing {
|
||||
+ // https://en.wikipedia.org/wiki/BLAKE_(hash_function)
|
||||
+ // https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java
|
||||
+
|
||||
+ private final static long[] blake2b_IV = {
|
||||
+ 0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL,
|
||||
+ 0xa54ff53a5f1d36f1L, 0x510e527fade682d1L, 0x9b05688c2b3e6c1fL,
|
||||
+ 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L
|
||||
+ };
|
||||
+
|
||||
+ private final static byte[][] blake2b_sigma = {
|
||||
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
+ {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||
+ {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
|
||||
+ {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
|
||||
+ {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
|
||||
+ {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
|
||||
+ {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
|
||||
+ {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
|
||||
+ {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
|
||||
+ {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0},
|
||||
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
+ {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}
|
||||
+ };
|
||||
+
|
||||
+ public static long[] hashWorldSeed(long[] worldSeed) {
|
||||
+ long[] result = blake2b_IV.clone();
|
||||
+ result[0] ^= 0x01010040;
|
||||
+ hash(worldSeed, result, new long[16], 0, false);
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ public static void hash(long[] message, long[] chainValue, long[] internalState, long messageOffset, boolean isFinal) {
|
||||
+ assert message.length == 16;
|
||||
+ assert chainValue.length == 8;
|
||||
+ assert internalState.length == 16;
|
||||
+
|
||||
+ System.arraycopy(chainValue, 0, internalState, 0, chainValue.length);
|
||||
+ System.arraycopy(blake2b_IV, 0, internalState, chainValue.length, 4);
|
||||
+ internalState[12] = messageOffset ^ blake2b_IV[4];
|
||||
+ internalState[13] = blake2b_IV[5];
|
||||
+ if (isFinal) internalState[14] = ~blake2b_IV[6];
|
||||
+ internalState[15] = blake2b_IV[7];
|
||||
+
|
||||
+ for (int round = 0; round < 12; round++) {
|
||||
+ G(message[blake2b_sigma[round][0]], message[blake2b_sigma[round][1]], 0, 4, 8, 12, internalState);
|
||||
+ G(message[blake2b_sigma[round][2]], message[blake2b_sigma[round][3]], 1, 5, 9, 13, internalState);
|
||||
+ G(message[blake2b_sigma[round][4]], message[blake2b_sigma[round][5]], 2, 6, 10, 14, internalState);
|
||||
+ G(message[blake2b_sigma[round][6]], message[blake2b_sigma[round][7]], 3, 7, 11, 15, internalState);
|
||||
+ G(message[blake2b_sigma[round][8]], message[blake2b_sigma[round][9]], 0, 5, 10, 15, internalState);
|
||||
+ G(message[blake2b_sigma[round][10]], message[blake2b_sigma[round][11]], 1, 6, 11, 12, internalState);
|
||||
+ G(message[blake2b_sigma[round][12]], message[blake2b_sigma[round][13]], 2, 7, 8, 13, internalState);
|
||||
+ G(message[blake2b_sigma[round][14]], message[blake2b_sigma[round][15]], 3, 4, 9, 14, internalState);
|
||||
+ }
|
||||
+
|
||||
+ for (int i = 0; i < 8; i++) {
|
||||
+ chainValue[i] ^= internalState[i] ^ internalState[i + 8];
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void G(long m1, long m2, int posA, int posB, int posC, int posD, long[] internalState) {
|
||||
+ internalState[posA] = internalState[posA] + internalState[posB] + m1;
|
||||
+ internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 32);
|
||||
+ internalState[posC] = internalState[posC] + internalState[posD];
|
||||
+ internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE
|
||||
+ internalState[posA] = internalState[posA] + internalState[posB] + m2;
|
||||
+ internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 16);
|
||||
+ internalState[posC] = internalState[posC] + internalState[posD];
|
||||
+ internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE
|
||||
+ }
|
||||
+}
|
||||
@@ -0,0 +1,162 @@
|
||||
--- /dev/null
|
||||
+++ b/src/main/java/su/plo/matter/WorldgenCryptoRandom.java
|
||||
@@ -1,0 +_,159 @@
|
||||
+package su.plo.matter;
|
||||
+
|
||||
+import net.minecraft.util.Mth;
|
||||
+import net.minecraft.util.RandomSource;
|
||||
+import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
+import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
+import org.jetbrains.annotations.NotNull;
|
||||
+
|
||||
+import java.util.Arrays;
|
||||
+
|
||||
+public class WorldgenCryptoRandom extends WorldgenRandom {
|
||||
+ // hash the world seed to guard against badly chosen world seeds
|
||||
+ private static final long[] HASHED_ZERO_SEED = Hashing.hashWorldSeed(new long[Globals.WORLD_SEED_LONGS]);
|
||||
+ private static final ThreadLocal<long[]> LAST_SEEN_WORLD_SEED = ThreadLocal.withInitial(() -> new long[Globals.WORLD_SEED_LONGS]);
|
||||
+ private static final ThreadLocal<long[]> HASHED_WORLD_SEED = ThreadLocal.withInitial(() -> HASHED_ZERO_SEED);
|
||||
+
|
||||
+ private final long[] worldSeed = new long[Globals.WORLD_SEED_LONGS];
|
||||
+ private final long[] randomBits = new long[8];
|
||||
+ private int randomBitIndex;
|
||||
+ private static final int MAX_RANDOM_BIT_INDEX = 64 * 8;
|
||||
+ private static final int LOG2_MAX_RANDOM_BIT_INDEX = 9;
|
||||
+ private long counter;
|
||||
+ private final long[] message = new long[16];
|
||||
+ private final long[] cachedInternalState = new long[16];
|
||||
+
|
||||
+ public WorldgenCryptoRandom(int x, int z, Globals.Salt typeSalt, long salt) {
|
||||
+ super(new LegacyRandomSource(0L));
|
||||
+ if (typeSalt != null) {
|
||||
+ this.setSecureSeed(x, z, typeSalt, salt);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public void setSecureSeed(int x, int z, Globals.Salt typeSalt, long salt) {
|
||||
+ System.arraycopy(Globals.worldSeed, 0, this.worldSeed, 0, Globals.WORLD_SEED_LONGS);
|
||||
+ message[0] = ((long) x << 32) | ((long) z & 0xffffffffL);
|
||||
+ message[1] = ((long) Globals.dimension.get() << 32) | ((long) salt & 0xffffffffL);
|
||||
+ message[2] = typeSalt.ordinal();
|
||||
+ message[3] = counter = 0;
|
||||
+ randomBitIndex = MAX_RANDOM_BIT_INDEX;
|
||||
+ }
|
||||
+
|
||||
+ private long[] getHashedWorldSeed() {
|
||||
+ if (!Arrays.equals(worldSeed, LAST_SEEN_WORLD_SEED.get())) {
|
||||
+ HASHED_WORLD_SEED.set(Hashing.hashWorldSeed(worldSeed));
|
||||
+ System.arraycopy(worldSeed, 0, LAST_SEEN_WORLD_SEED.get(), 0, Globals.WORLD_SEED_LONGS);
|
||||
+ }
|
||||
+ return HASHED_WORLD_SEED.get();
|
||||
+ }
|
||||
+
|
||||
+ private void moreRandomBits() {
|
||||
+ message[3] = counter++;
|
||||
+ System.arraycopy(getHashedWorldSeed(), 0, randomBits, 0, 8);
|
||||
+ Hashing.hash(message, randomBits, cachedInternalState, 64, true);
|
||||
+ }
|
||||
+
|
||||
+ private long getBits(int count) {
|
||||
+ if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) {
|
||||
+ moreRandomBits();
|
||||
+ randomBitIndex -= MAX_RANDOM_BIT_INDEX;
|
||||
+ }
|
||||
+
|
||||
+ int alignment = randomBitIndex & 63;
|
||||
+ if ((randomBitIndex >>> 6) == ((randomBitIndex + count) >>> 6)) {
|
||||
+ long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << count) - 1);
|
||||
+ randomBitIndex += count;
|
||||
+ return result;
|
||||
+ } else {
|
||||
+ long result = (randomBits[randomBitIndex >>> 6] >>> alignment) & ((1L << (64 - alignment)) - 1);
|
||||
+ randomBitIndex += count;
|
||||
+ if (randomBitIndex >= MAX_RANDOM_BIT_INDEX) {
|
||||
+ moreRandomBits();
|
||||
+ randomBitIndex -= MAX_RANDOM_BIT_INDEX;
|
||||
+ }
|
||||
+ alignment = randomBitIndex & 63;
|
||||
+ result <<= alignment;
|
||||
+ result |= (randomBits[randomBitIndex >>> 6] >>> (64 - alignment)) & ((1L << alignment) - 1);
|
||||
+
|
||||
+ return result;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public @NotNull RandomSource fork() {
|
||||
+ WorldgenCryptoRandom fork = new WorldgenCryptoRandom(0, 0, null, 0);
|
||||
+
|
||||
+ System.arraycopy(Globals.worldSeed, 0, fork.worldSeed, 0, Globals.WORLD_SEED_LONGS);
|
||||
+ fork.message[0] = this.message[0];
|
||||
+ fork.message[1] = this.message[1];
|
||||
+ fork.message[2] = this.message[2];
|
||||
+ fork.message[3] = this.message[3];
|
||||
+ fork.randomBitIndex = this.randomBitIndex;
|
||||
+ fork.counter = this.counter;
|
||||
+ fork.nextLong();
|
||||
+
|
||||
+ return fork;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int next(int bits) {
|
||||
+ return (int) getBits(bits);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void consumeCount(int count) {
|
||||
+ randomBitIndex += count;
|
||||
+ if (randomBitIndex >= MAX_RANDOM_BIT_INDEX * 2) {
|
||||
+ randomBitIndex -= MAX_RANDOM_BIT_INDEX;
|
||||
+ counter += randomBitIndex >>> LOG2_MAX_RANDOM_BIT_INDEX;
|
||||
+ randomBitIndex &= MAX_RANDOM_BIT_INDEX - 1;
|
||||
+ randomBitIndex += MAX_RANDOM_BIT_INDEX;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public int nextInt(int bound) {
|
||||
+ int bits = Mth.ceillog2(bound);
|
||||
+ int result;
|
||||
+ do {
|
||||
+ result = (int) getBits(bits);
|
||||
+ } while (result >= bound);
|
||||
+
|
||||
+ return result;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public long nextLong() {
|
||||
+ return getBits(64);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public double nextDouble() {
|
||||
+ return getBits(53) * 0x1.0p-53;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public long setDecorationSeed(long worldSeed, int blockX, int blockZ) {
|
||||
+ setSecureSeed(blockX, blockZ, Globals.Salt.POPULATION, 0);
|
||||
+ return ((long) blockX << 32) | ((long) blockZ & 0xffffffffL);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setFeatureSeed(long populationSeed, int index, int step) {
|
||||
+ setSecureSeed((int) (populationSeed >> 32), (int) populationSeed, Globals.Salt.DECORATION, index + 10000L * step);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setLargeFeatureSeed(long worldSeed, int chunkX, int chunkZ) {
|
||||
+ super.setLargeFeatureSeed(worldSeed, chunkX, chunkZ);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setLargeFeatureWithSalt(long worldSeed, int regionX, int regionZ, int salt) {
|
||||
+ super.setLargeFeatureWithSalt(worldSeed, regionX, regionZ, salt);
|
||||
+ }
|
||||
+
|
||||
+ public static RandomSource seedSlimeChunk(int chunkX, int chunkZ) {
|
||||
+ return new WorldgenCryptoRandom(chunkX, chunkZ, Globals.Salt.SLIME_CHUNK, 0);
|
||||
+ }
|
||||
+}
|
||||
Reference in New Issue
Block a user