diff --git a/orebfuscator-api/src/main/java/net/imprex/orebfuscator/api/OrebfuscatorService.java b/orebfuscator-api/src/main/java/net/imprex/orebfuscator/api/OrebfuscatorService.java index 9a0d8bf7..4f996253 100644 --- a/orebfuscator-api/src/main/java/net/imprex/orebfuscator/api/OrebfuscatorService.java +++ b/orebfuscator-api/src/main/java/net/imprex/orebfuscator/api/OrebfuscatorService.java @@ -1,8 +1,8 @@ package net.imprex.orebfuscator.api; import java.util.Collection; - import org.bukkit.block.Block; +import org.jspecify.annotations.NullMarked; /** *

@@ -16,6 +16,7 @@ * * @since 5.2.0 */ +@NullMarked public interface OrebfuscatorService { /** diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorCompatibility.java b/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorCompatibility.java index 36257427..f8961432 100644 --- a/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorCompatibility.java +++ b/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorCompatibility.java @@ -1,18 +1,12 @@ package net.imprex.orebfuscator; -import java.lang.reflect.Constructor; -import java.util.concurrent.CompletableFuture; - -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - import dev.imprex.orebfuscator.config.api.Config; import dev.imprex.orebfuscator.logging.OfcLogger; -import dev.imprex.orebfuscator.util.ChunkCacheKey; +import java.lang.reflect.Constructor; import net.imprex.orebfuscator.compatibility.CompatibilityLayer; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.imprex.orebfuscator.util.ServerVersion; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; public class OrebfuscatorCompatibility { @@ -23,7 +17,7 @@ public static void initialize(Plugin plugin, Config config) { throw new IllegalStateException("Compatibility layer is already initialized!"); } - String className = "net.imprex.orebfuscator.compatibility.bukkit.BukkitCompatibilityLayer"; + String className = "net.imprex.orebfuscator.compatibility.spigot.SpigotCompatibilityLayer"; if (ServerVersion.isFolia()) { className = "net.imprex.orebfuscator.compatibility.folia.FoliaCompatibilityLayer"; } else if (ServerVersion.isPaper()) { @@ -56,18 +50,6 @@ public static void runAsyncNow(Runnable runnable) { instance.getScheduler().runAsyncNow(runnable); } - public static void runAsyncAtFixedRate(Runnable runnable, long delay, long period) { - instance.getScheduler().runAsyncAtFixedRate(runnable, delay, period); - } - - public static void cancelTasks() { - instance.getScheduler().cancelTasks(); - } - - public static CompletableFuture getNeighboringChunks(World world, ChunkCacheKey key) { - return instance.getNeighboringChunks(world, key); - } - public static void close() { if (instance != null) { instance.getScheduler().cancelTasks(); diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/compatibility/CompatibilityLayer.java b/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/compatibility/CompatibilityLayer.java index 6a039e64..f80845aa 100644 --- a/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/compatibility/CompatibilityLayer.java +++ b/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/compatibility/CompatibilityLayer.java @@ -1,17 +1,8 @@ package net.imprex.orebfuscator.compatibility; -import java.util.concurrent.CompletableFuture; - -import org.bukkit.World; - -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; - public interface CompatibilityLayer { boolean isGameThread(); CompatibilityScheduler getScheduler(); - - CompletableFuture getNeighboringChunks(World world, ChunkCacheKey key); } diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/compatibility/CompatibilityScheduler.java b/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/compatibility/CompatibilityScheduler.java index 37d1436b..a9ac2ba2 100644 --- a/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/compatibility/CompatibilityScheduler.java +++ b/orebfuscator-compatibility/orebfuscator-compatibility-api/src/main/java/net/imprex/orebfuscator/compatibility/CompatibilityScheduler.java @@ -8,7 +8,5 @@ public interface CompatibilityScheduler { void runAsyncNow(Runnable runnable); - void runAsyncAtFixedRate(Runnable runnable, long delay, long period); - void cancelTasks(); } diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitChunkLoader.java b/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitChunkLoader.java deleted file mode 100644 index f8dd4c3d..00000000 --- a/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitChunkLoader.java +++ /dev/null @@ -1,72 +0,0 @@ -package net.imprex.orebfuscator.compatibility.bukkit; - -import java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; - -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.plugin.Plugin; - -import dev.imprex.orebfuscator.config.api.Config; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import dev.imprex.orebfuscator.util.ChunkDirection; -import net.imprex.orebfuscator.OrebfuscatorNms; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; - -public class BukkitChunkLoader implements Runnable { - - private final Queue requests = new ConcurrentLinkedQueue<>(); - - private final long availableNanosPerTick; - - public BukkitChunkLoader(Plugin plugin, Config config) { - this.availableNanosPerTick = TimeUnit.MILLISECONDS.toNanos(config.advanced().maxMillisecondsPerTick()); - - Bukkit.getScheduler().runTaskTimer(plugin, this, 0, 1); - } - - public CompletableFuture submitRequest(World world, ChunkCacheKey key) { - Request request = new Request(world, key); - this.requests.offer(request); - return request.future; - } - - @Override - public void run() { - final long time = System.nanoTime(); - - Request request = null; - while (System.nanoTime() - time < this.availableNanosPerTick && (request = this.requests.poll()) != null) { - request.run(); - } - } - - private class Request implements Runnable { - - private final World world; - private final ChunkCacheKey key; - - private final CompletableFuture future = new CompletableFuture<>(); - - public Request(World world, ChunkCacheKey key) { - this.world = world; - this.key = key; - } - - @Override - public void run() { - final ReadOnlyChunk[] neighboringChunks = new ReadOnlyChunk[4]; - - for (ChunkDirection direction : ChunkDirection.values()) { - int chunkX = key.x() + direction.getOffsetX(); - int chunkZ = key.z() + direction.getOffsetZ(); - - neighboringChunks[direction.ordinal()] = OrebfuscatorNms.getReadOnlyChunk(world, chunkX, chunkZ); - } - - future.complete(neighboringChunks); - } - } -} diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitCompatibilityLayer.java b/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitCompatibilityLayer.java deleted file mode 100644 index 637ca123..00000000 --- a/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitCompatibilityLayer.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.imprex.orebfuscator.compatibility.bukkit; - -import java.util.concurrent.CompletableFuture; - -import org.bukkit.World; -import org.bukkit.plugin.Plugin; - -import dev.imprex.orebfuscator.config.api.Config; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.imprex.orebfuscator.compatibility.CompatibilityLayer; -import net.imprex.orebfuscator.compatibility.CompatibilityScheduler; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; - -public class BukkitCompatibilityLayer implements CompatibilityLayer { - - private final Thread mainThread = Thread.currentThread(); - - private final BukkitScheduler scheduler; - private final BukkitChunkLoader chunkLoader; - - public BukkitCompatibilityLayer(Plugin plugin, Config config) { - this.scheduler = new BukkitScheduler(plugin); - this.chunkLoader = new BukkitChunkLoader(plugin, config); - } - - @Override - public boolean isGameThread() { - return Thread.currentThread() == this.mainThread; - } - - @Override - public CompatibilityScheduler getScheduler() { - return this.scheduler; - } - - @Override - public CompletableFuture getNeighboringChunks(World world, ChunkCacheKey key) { - return this.chunkLoader.submitRequest(world, key); - } -} diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/spigot/SpigotCompatibilityLayer.java b/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/spigot/SpigotCompatibilityLayer.java new file mode 100644 index 00000000..8c98c1fb --- /dev/null +++ b/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/spigot/SpigotCompatibilityLayer.java @@ -0,0 +1,27 @@ +package net.imprex.orebfuscator.compatibility.spigot; + +import dev.imprex.orebfuscator.config.api.Config; +import net.imprex.orebfuscator.compatibility.CompatibilityLayer; +import net.imprex.orebfuscator.compatibility.CompatibilityScheduler; +import org.bukkit.plugin.Plugin; + +public class SpigotCompatibilityLayer implements CompatibilityLayer { + + private final Thread mainThread = Thread.currentThread(); + + private final SpigotScheduler scheduler; + + public SpigotCompatibilityLayer(Plugin plugin, Config config) { + this.scheduler = new SpigotScheduler(plugin); + } + + @Override + public boolean isGameThread() { + return Thread.currentThread() == this.mainThread; + } + + @Override + public CompatibilityScheduler getScheduler() { + return this.scheduler; + } +} diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitScheduler.java b/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/spigot/SpigotScheduler.java similarity index 63% rename from orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitScheduler.java rename to orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/spigot/SpigotScheduler.java index c5ce35ef..0dc6fe71 100644 --- a/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/bukkit/BukkitScheduler.java +++ b/orebfuscator-compatibility/orebfuscator-compatibility-bukkit/src/main/java/net/imprex/orebfuscator/compatibility/spigot/SpigotScheduler.java @@ -1,16 +1,15 @@ -package net.imprex.orebfuscator.compatibility.bukkit; +package net.imprex.orebfuscator.compatibility.spigot; +import net.imprex.orebfuscator.compatibility.CompatibilityScheduler; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import net.imprex.orebfuscator.compatibility.CompatibilityScheduler; - -public class BukkitScheduler implements CompatibilityScheduler { +public class SpigotScheduler implements CompatibilityScheduler { private final Plugin plugin; - public BukkitScheduler(Plugin plugin) { + public SpigotScheduler(Plugin plugin) { this.plugin = plugin; } @@ -28,13 +27,6 @@ public void runAsyncNow(Runnable runnable) { } } - @Override - public void runAsyncAtFixedRate(Runnable runnable, long delay, long period) { - if (this.plugin.isEnabled()) { - Bukkit.getScheduler().runTaskTimerAsynchronously(this.plugin, runnable, delay, period); - } - } - @Override public void cancelTasks() { Bukkit.getScheduler().cancelTasks(this.plugin); diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-folia/src/main/java/net/imprex/orebfuscator/compatibility/folia/FoliaCompatibilityLayer.java b/orebfuscator-compatibility/orebfuscator-compatibility-folia/src/main/java/net/imprex/orebfuscator/compatibility/folia/FoliaCompatibilityLayer.java index ab557a86..1ab1984e 100644 --- a/orebfuscator-compatibility/orebfuscator-compatibility-folia/src/main/java/net/imprex/orebfuscator/compatibility/folia/FoliaCompatibilityLayer.java +++ b/orebfuscator-compatibility/orebfuscator-compatibility-folia/src/main/java/net/imprex/orebfuscator/compatibility/folia/FoliaCompatibilityLayer.java @@ -1,12 +1,11 @@ package net.imprex.orebfuscator.compatibility.folia; -import org.bukkit.plugin.Plugin; - import dev.imprex.orebfuscator.config.api.Config; +import net.imprex.orebfuscator.compatibility.CompatibilityLayer; import net.imprex.orebfuscator.compatibility.CompatibilityScheduler; -import net.imprex.orebfuscator.compatibility.paper.AbstractPaperCompatibilityLayer; +import org.bukkit.plugin.Plugin; -public class FoliaCompatibilityLayer extends AbstractPaperCompatibilityLayer { +public class FoliaCompatibilityLayer implements CompatibilityLayer { private static final Class TICK_THREAD_CLASS = getTickThreadClass(); diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-folia/src/main/java/net/imprex/orebfuscator/compatibility/folia/FoliaScheduler.java b/orebfuscator-compatibility/orebfuscator-compatibility-folia/src/main/java/net/imprex/orebfuscator/compatibility/folia/FoliaScheduler.java index 677713fa..e76693b2 100644 --- a/orebfuscator-compatibility/orebfuscator-compatibility-folia/src/main/java/net/imprex/orebfuscator/compatibility/folia/FoliaScheduler.java +++ b/orebfuscator-compatibility/orebfuscator-compatibility-folia/src/main/java/net/imprex/orebfuscator/compatibility/folia/FoliaScheduler.java @@ -1,13 +1,10 @@ package net.imprex.orebfuscator.compatibility.folia; -import java.util.concurrent.TimeUnit; - +import net.imprex.orebfuscator.compatibility.CompatibilityScheduler; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import net.imprex.orebfuscator.compatibility.CompatibilityScheduler; - public class FoliaScheduler implements CompatibilityScheduler { private final Plugin plugin; @@ -30,14 +27,6 @@ public void runAsyncNow(Runnable runnable) { } } - @Override - public void runAsyncAtFixedRate(Runnable runnable, long delay, long period) { - if (this.plugin.isEnabled()) { - Bukkit.getAsyncScheduler().runAtFixedRate(this.plugin, task -> runnable.run(), - delay * 50, period * 50, TimeUnit.MILLISECONDS); - } - } - @Override public void cancelTasks() { Bukkit.getAsyncScheduler().cancelTasks(this.plugin); diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-paper/src/main/java/net/imprex/orebfuscator/compatibility/paper/AbstractPaperCompatibilityLayer.java b/orebfuscator-compatibility/orebfuscator-compatibility-paper/src/main/java/net/imprex/orebfuscator/compatibility/paper/AbstractPaperCompatibilityLayer.java deleted file mode 100644 index 3f5efe34..00000000 --- a/orebfuscator-compatibility/orebfuscator-compatibility-paper/src/main/java/net/imprex/orebfuscator/compatibility/paper/AbstractPaperCompatibilityLayer.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.imprex.orebfuscator.compatibility.paper; - -import java.util.concurrent.CompletableFuture; - -import org.bukkit.World; - -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import dev.imprex.orebfuscator.util.ChunkDirection; -import net.imprex.orebfuscator.OrebfuscatorNms; -import net.imprex.orebfuscator.compatibility.CompatibilityLayer; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; - -public abstract class AbstractPaperCompatibilityLayer implements CompatibilityLayer { - - @Override - public CompletableFuture getNeighboringChunks(World world, ChunkCacheKey key) { - CompletableFuture[] futures = new CompletableFuture[4]; - ReadOnlyChunk[] neighboringChunks = new ReadOnlyChunk[4]; - - for (ChunkDirection direction : ChunkDirection.values()) { - int chunkX = key.x() + direction.getOffsetX(); - int chunkZ = key.z() + direction.getOffsetZ(); - int index = direction.ordinal(); - - futures[index] = world.getChunkAtAsync(chunkX, chunkZ).thenAccept(chunk -> { - neighboringChunks[index] = OrebfuscatorNms.getReadOnlyChunk(world, chunkX, chunkZ); - }); - } - - return CompletableFuture.allOf(futures).thenApply(v -> neighboringChunks); - } -} diff --git a/orebfuscator-compatibility/orebfuscator-compatibility-paper/src/main/java/net/imprex/orebfuscator/compatibility/paper/PaperCompatibilityLayer.java b/orebfuscator-compatibility/orebfuscator-compatibility-paper/src/main/java/net/imprex/orebfuscator/compatibility/paper/PaperCompatibilityLayer.java index a72d8a23..d8047c4d 100644 --- a/orebfuscator-compatibility/orebfuscator-compatibility-paper/src/main/java/net/imprex/orebfuscator/compatibility/paper/PaperCompatibilityLayer.java +++ b/orebfuscator-compatibility/orebfuscator-compatibility-paper/src/main/java/net/imprex/orebfuscator/compatibility/paper/PaperCompatibilityLayer.java @@ -1,19 +1,19 @@ package net.imprex.orebfuscator.compatibility.paper; -import org.bukkit.plugin.Plugin; - import dev.imprex.orebfuscator.config.api.Config; +import net.imprex.orebfuscator.compatibility.CompatibilityLayer; import net.imprex.orebfuscator.compatibility.CompatibilityScheduler; -import net.imprex.orebfuscator.compatibility.bukkit.BukkitScheduler; +import net.imprex.orebfuscator.compatibility.spigot.SpigotScheduler; +import org.bukkit.plugin.Plugin; -public class PaperCompatibilityLayer extends AbstractPaperCompatibilityLayer { +public class PaperCompatibilityLayer implements CompatibilityLayer { private final Thread mainThread = Thread.currentThread(); - private final BukkitScheduler scheduler; + private final SpigotScheduler scheduler; public PaperCompatibilityLayer(Plugin plugin, Config config) { - this.scheduler = new BukkitScheduler(plugin); + this.scheduler = new SpigotScheduler(plugin); } @Override diff --git a/orebfuscator-core/pom.xml b/orebfuscator-core/pom.xml index 9afa7bfa..6a6918a0 100644 --- a/orebfuscator-core/pom.xml +++ b/orebfuscator-core/pom.xml @@ -53,12 +53,12 @@ ${dependency.joml.version} provided - + provided + diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/OrebfuscatorDumpFile.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/OrebfuscatorDumpFile.java new file mode 100644 index 00000000..40e5aaab --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/OrebfuscatorDumpFile.java @@ -0,0 +1,135 @@ +package dev.imprex.orebfuscator; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonIOException; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonWriter; +import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.util.JavaVersion; +import java.io.BufferedWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.util.Base64; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public class OrebfuscatorDumpFile extends ConfigurationSection { + + private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ofPattern("uuuu-MM-dd_HH.mm.ss"); + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting() + .registerTypeAdapter(ConfigurationSection.class, new Json()) + .create(); + + private final OrebfuscatorCore orebfuscator; + private final TemporalAccessor now; + + public OrebfuscatorDumpFile(OrebfuscatorCore orebfuscator) { + super(""); + + this.orebfuscator = orebfuscator; + this.now = OffsetDateTime.now(ZoneOffset.UTC); + + this.initialize(); + } + + private void initialize() { + set("timestamp", TIME_FORMAT.format(now)); + + set("versions.java", JavaVersion.get()); + set("versions.orebfuscator", orebfuscator.orebfuscatorVersion().toString()); + + var statistics = createSection("statistics"); + orebfuscator.statisticsRegistry().entries().stream() + .sorted(Comparator.comparing(Map.Entry::getKey)) + .forEach(entry -> statistics.set(entry.getKey(), entry.getValue())); + + var levels = createSection("levels"); + for (WorldAccessor accessor : orebfuscator.worlds()) { + var section = levels.createSection(accessor.name()); + section.set("accessor", accessor); + section.set("minY", accessor.minBuildHeight()); + section.set("maxY", accessor.maxBuildHeight()); + } + + orebfuscator.config().dumpBlocks(createSection("blocks")); + + Base64.Encoder encoder = Base64.getUrlEncoder(); + String latestLog = OfcLogger.getLatestVerboseLog(); + set("verboseLog", encoder.encodeToString(latestLog.getBytes(StandardCharsets.UTF_8))); + + try { + Path configPath = orebfuscator.configDirectory().resolve("config.yml"); + String config = String.join("\n", Files.readAllLines(configPath)); + set("config", encoder.encodeToString(config.getBytes(StandardCharsets.UTF_8))); + } catch (IOException e) { + OfcLogger.error(e); + } + + String configReport = orebfuscator.config().report(); + configReport = configReport != null ? configReport : ""; + set("configReport", encoder.encodeToString(configReport.getBytes(StandardCharsets.UTF_8))); + } + + public Path write() { + Path path = orebfuscator.configDirectory().resolve("dump-" + FILE_FORMAT.format(now) + ".json"); + try (BufferedWriter writer = Files.newBufferedWriter(path)) { + GSON.toJson(this, writer); + } catch (IOException e) { + OfcLogger.error(e); + } + + return path; + } + + private static class Json implements JsonSerializer { + + @Override + public JsonElement serialize(ConfigurationSection section, Type type, JsonSerializationContext jsonSerializationContext) { + return serialize(section); + } + + private JsonElement serialize(Object value) { + if (value instanceof Boolean booleanValue) { + return new JsonPrimitive(booleanValue); + } else if (value instanceof Number numberValue) { + return new JsonPrimitive(numberValue); + } else if (value instanceof String stringValue) { + return new JsonPrimitive(stringValue); + } else if (value instanceof List list) { + JsonArray json = new JsonArray(list.size()); + for (Object item : list) { + json.add(serialize(item)); + } + return json; + } else if (value instanceof ConfigurationSection section) { + JsonObject json = new JsonObject(); + for (String key : section.getKeys()) { + json.add(key, serialize(section.get(key))); + } + return json; + } + + throw new JsonIOException("Unexpected type " + value.getClass()); + } + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/PermissionRequirements.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/PermissionRequirements.java new file mode 100644 index 00000000..50d1fb49 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/PermissionRequirements.java @@ -0,0 +1,20 @@ +package dev.imprex.orebfuscator; + +import java.util.Optional; +import java.util.OptionalInt; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record PermissionRequirements(OptionalInt operatorLevel, Optional permission) { + + public static final PermissionRequirements BYPASS = new PermissionRequirements(OptionalInt.empty(), + Optional.of("orebfuscator.bypass")); + public static final PermissionRequirements ADMIN = new PermissionRequirements(OptionalInt.of(4), + Optional.of("orebfuscator.admin")); + + public PermissionRequirements { + if (operatorLevel.isEmpty() && permission.isEmpty()) { + throw new IllegalArgumentException("Either an operator level, permission or both have to defined"); + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/UpdateSystem.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/UpdateSystem.java similarity index 68% rename from orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/UpdateSystem.java rename to orebfuscator-core/src/main/java/dev/imprex/orebfuscator/UpdateSystem.java index 957983ca..8c7d6450 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/UpdateSystem.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/UpdateSystem.java @@ -1,5 +1,13 @@ -package net.imprex.orebfuscator; +package dev.imprex.orebfuscator; +import com.google.gson.annotations.SerializedName; +import dev.imprex.orebfuscator.config.api.GeneralConfig; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.logging.LogLevel; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.util.AbstractHttpService; +import dev.imprex.orebfuscator.util.ConsoleUtil; +import dev.imprex.orebfuscator.util.Version; import java.time.Duration; import java.time.Instant; import java.util.Arrays; @@ -12,30 +20,20 @@ import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -import org.bukkit.entity.Player; - -import com.google.gson.annotations.SerializedName; - -import dev.imprex.orebfuscator.config.api.GeneralConfig; -import dev.imprex.orebfuscator.logging.LogLevel; -import dev.imprex.orebfuscator.logging.OfcLogger; -import dev.imprex.orebfuscator.util.Version; -import net.imprex.orebfuscator.util.AbstractHttpService; -import net.imprex.orebfuscator.util.ConsoleUtil; -import net.imprex.orebfuscator.util.MinecraftVersion; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.ClickEvent; -import net.md_5.bungee.api.chat.ComponentBuilder; -import net.md_5.bungee.api.chat.HoverEvent; -import net.md_5.bungee.api.chat.hover.content.Text; - +@NullMarked public class UpdateSystem extends AbstractHttpService { - private static final Pattern DEV_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(?:-b(?\\d+))?"); + private static final Pattern DEV_VERSION_PATTERN = Pattern.compile("(?:-b(?\\d+))?"); + + private static boolean isDevVersion(Version version) { + if (version.suffix() == null) { + return false; + } - private static boolean isDevVersion(String version) { - Matcher matcher = DEV_VERSION_PATTERN.matcher(version); + Matcher matcher = DEV_VERSION_PATTERN.matcher(version.suffix()); return matcher.find() && matcher.group("build") != null; } @@ -44,43 +42,46 @@ private static boolean isDevVersion(String version) { private static final Duration CACHE_DURATION = Duration.ofMinutes(10L); - private final Orebfuscator orebfuscator; + private final String loader; + + private final OrebfuscatorCore orebfuscator; private final GeneralConfig generalConfig; - private final AtomicReference validUntil = new AtomicReference<>(); - private final AtomicReference>> latestVersion = new AtomicReference<>(); + private final AtomicReference<@Nullable Instant> validUntil = new AtomicReference<>(); + private final AtomicReference<@Nullable CompletableFuture>> latestVersion + = new AtomicReference<>(); - public UpdateSystem(Orebfuscator orebfuscator) { + public UpdateSystem(OrebfuscatorCore orebfuscator, String loader) { super(orebfuscator); + this.loader = loader; + this.orebfuscator = orebfuscator; - this.generalConfig = orebfuscator.getOrebfuscatorConfig().general(); + this.generalConfig = orebfuscator.config().general(); this.checkForUpdates(); } private CompletableFuture> requestLatestVersion() { - String installedVersion = this.orebfuscator.getDescription().getVersion(); + Version installedVersion = this.orebfuscator.orebfuscatorVersion(); if (!this.generalConfig.checkForUpdates() || isDevVersion(installedVersion)) { OfcLogger.debug("UpdateSystem - Update check disabled or dev version detected; skipping"); return CompletableFuture.completedFuture(Optional.empty()); } - var uri = String.format(API_URI, "bukkit", MinecraftVersion.current()); + var uri = String.format(API_URI, this.loader, this.orebfuscator.minecraftVersion()); return HTTP.sendAsync(request(uri).build(), optionalJson(ModrinthVersion[].class)).thenApply(response -> response.body().flatMap(body -> { - var version = Version.parse(installedVersion); var latestVersion = Arrays.stream(body) .filter(e -> Objects.equals(e.versionType, "release")) .filter(e -> Objects.equals(e.status, "listed")) - .sorted(Comparator.reverseOrder()) - .findFirst(); + .max(Comparator.naturalOrder()); latestVersion.ifPresentOrElse( v -> OfcLogger.debug("UpdateSystem - Fetched latest version " + v.version), () -> OfcLogger.debug("UpdateSystem - Couldn't fetch latest version")); - return latestVersion.map(v -> version.isBelow(v.version) ? v : null); + return latestVersion.map(v -> installedVersion.isBelow(v.version) ? v : null); }) ).exceptionally(throwable -> { OfcLogger.log(LogLevel.WARN, "UpdateSystem - Unable to fetch latest version", throwable); @@ -118,31 +119,24 @@ private void ifNewerVersionAvailable(Consumer consumer) { this.getLatestVersion().thenAccept(o -> o.ifPresent(consumer)); } - private void checkForUpdates() { + public void ifNewerDownloadAvailable(Consumer consumer) { this.ifNewerVersionAvailable(version -> { String downloadUri = String.format(DOWNLOAD_URI, version.version); - ConsoleUtil.printBox(LogLevel.WARN, "UPDATE AVAILABLE", "", downloadUri); + consumer.accept(downloadUri); }); } - public void checkForUpdates(Player player) { + private void checkForUpdates() { this.ifNewerVersionAvailable(version -> { String downloadUri = String.format(DOWNLOAD_URI, version.version); - BaseComponent[] components = new ComponentBuilder("[§bOrebfuscator§f]§7 A new release is available ") - .append("§f§l[CLICK HERE]") - .event(new ClickEvent(ClickEvent.Action.OPEN_URL, downloadUri)) - .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, - new Text(new ComponentBuilder("§7Click here to see the latest release").create()))).create(); - OrebfuscatorCompatibility.runForPlayer(player, () -> { - player.spigot().sendMessage(components); - }); + ConsoleUtil.printBox(LogLevel.WARN, "UPDATE AVAILABLE", "", downloadUri); }); } public static class ModrinthVersion implements Comparable { private static final Comparator COMPARATOR = - Comparator.comparing(e -> e.version, Comparator.nullsLast(Version::compareTo)); + Comparator.comparing(e -> e.version, Comparator.nullsFirst(Version::compareTo)); @SerializedName("version_number") public Version version; diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/AbstractRegionFileCache.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/AbstractRegionFileCache.java index 46609b35..2a8fcff4 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/AbstractRegionFileCache.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/AbstractRegionFileCache.java @@ -1,5 +1,6 @@ package dev.imprex.orebfuscator.cache; +import dev.imprex.orebfuscator.logging.OfcLogger; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -14,10 +15,13 @@ import dev.imprex.orebfuscator.reflect.accessor.MethodAccessor; import dev.imprex.orebfuscator.util.ChunkCacheKey; import dev.imprex.orebfuscator.util.SimpleCache; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public abstract class AbstractRegionFileCache { - private static MethodAccessor serverGetServer; + private static @Nullable MethodAccessor serverGetServer; protected static T serverHandle(Object server, Class targetClass) { if (serverGetServer == null) { @@ -46,29 +50,29 @@ public AbstractRegionFileCache(CacheConfig cacheConfig) { protected abstract void closeRegionFile(T t) throws IOException; - protected abstract DataInputStream createInputStream(T t, ChunkCacheKey key) throws IOException; + protected abstract @Nullable DataInputStream createInputStream(T t, ChunkCacheKey key) throws IOException; protected abstract DataOutputStream createOutputStream(T t, ChunkCacheKey key) throws IOException; - public final DataInputStream createInputStream(ChunkCacheKey key) throws IOException { + public final @Nullable DataInputStream createInputStream(ChunkCacheKey key) throws IOException { T t = this.get(this.cacheConfig.regionFile(key)); - return t != null ? this.createInputStream(t, key) : null; + return this.createInputStream(t, key); } public final DataOutputStream createOutputStream(ChunkCacheKey key) throws IOException { T t = this.get(this.cacheConfig.regionFile(key)); - return t != null ? this.createOutputStream(t, key) : null; + return this.createOutputStream(t, key); } - private final void remove(Map.Entry entry) { + private void remove(Map.Entry entry) { try { this.closeRegionFile(entry.getValue()); } catch (IOException e) { - e.printStackTrace(); + OfcLogger.error(e); } } - protected final T get(Path path) throws IOException { + private T get(Path path) throws IOException { this.lock.readLock().lock(); try { T t = this.regionFiles.get(path); @@ -123,11 +127,9 @@ public final void clear() { try { for (T t : this.regionFiles.values()) { try { - if (t != null) { - this.closeRegionFile(t); - } + this.closeRegionFile(t); } catch (IOException e) { - e.printStackTrace(); + OfcLogger.error(e); } } this.regionFiles.clear(); diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/AsyncChunkSerializer.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/AsyncChunkSerializer.java similarity index 64% rename from orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/AsyncChunkSerializer.java rename to orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/AsyncChunkSerializer.java index d99a6758..ae5c7f35 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/AsyncChunkSerializer.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/AsyncChunkSerializer.java @@ -1,4 +1,4 @@ -package net.imprex.orebfuscator.cache; +package dev.imprex.orebfuscator.cache; import java.io.IOException; import java.util.HashMap; @@ -9,13 +9,13 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; - -import org.jetbrains.annotations.NotNull; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.statistics.CacheStatistics; import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.imprex.orebfuscator.Orebfuscator; +import dev.imprex.orebfuscator.util.RollingTimer; /** * This class works similar to a bounded buffer for cache read and write requests but also functions as the only @@ -26,8 +26,11 @@ * @see Bound buffer * @see Memory ordering */ +@NullMarked public class AsyncChunkSerializer implements Runnable { + private final CacheStatistics statistics; + private final Lock lock = new ReentrantLock(true); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); @@ -41,19 +44,19 @@ public class AsyncChunkSerializer implements Runnable { private final Thread thread; private volatile boolean running = true; - public AsyncChunkSerializer(Orebfuscator orebfuscator, AbstractRegionFileCache regionFileCache) { - this.maxTaskQueueSize = orebfuscator.getOrebfuscatorConfig().cache().maximumTaskQueueSize(); - this.serializer = new ChunkSerializer(regionFileCache); + public AsyncChunkSerializer(OrebfuscatorCore orebfuscator, AbstractRegionFileCache regionFileCache) { + this.statistics = orebfuscator.statistics().cache; + this.maxTaskQueueSize = orebfuscator.config().cache().maximumTaskQueueSize(); + this.serializer = new ChunkSerializer(regionFileCache, this.statistics); - this.thread = new Thread(Orebfuscator.THREAD_GROUP, this, "ofc-chunk-serializer"); + this.thread = new Thread(OrebfuscatorCore.THREAD_GROUP, this, "ofc-chunk-serializer"); this.thread.setDaemon(true); this.thread.start(); - orebfuscator.getStatistics().setDiskCacheQueueLengthSupplier(this.tasks::size); + this.statistics.setDiskCacheQueueLength(this.tasks::size); } - @NotNull - public CompletableFuture read(@NotNull ChunkCacheKey key) { + public CompletableFuture<@Nullable ChunkCacheEntry> read(ChunkCacheKey key) { this.lock.lock(); try { Runnable task = this.tasks.get(key); @@ -62,7 +65,7 @@ public CompletableFuture read(@NotNull ChunkCacheKey key) { } else if (task instanceof ReadTask) { return ((ReadTask) task).future; } else { - CompletableFuture future = new CompletableFuture<>(); + CompletableFuture future = new CompletableFuture<>(); this.queueTask(key, new ReadTask(key, future)); return future; } @@ -71,7 +74,7 @@ public CompletableFuture read(@NotNull ChunkCacheKey key) { } } - public void write(@NotNull ChunkCacheKey key, @NotNull CacheChunkEntry chunk) { + public void write(ChunkCacheKey key, ChunkCacheEntry chunk) { this.lock.lock(); try { Runnable prevTask = this.queueTask(key, new WriteTask(key, chunk)); @@ -83,8 +86,8 @@ public void write(@NotNull ChunkCacheKey key, @NotNull CacheChunkEntry chunk) { } } - @NotNull - private Runnable queueTask(@NotNull ChunkCacheKey key, @NotNull Runnable nextTask) { + @Nullable + private Runnable queueTask(ChunkCacheKey key, Runnable nextTask) { while (this.positions.size() >= this.maxTaskQueueSize) { this.notFull.awaitUninterruptibly(); } @@ -139,18 +142,43 @@ public void close() { } } - private class WriteTask implements Runnable { + private abstract class TimedTask implements Runnable { + + private final RollingTimer.Instance waitTimer = statistics.diskCacheWaitTime.start(); + private final RollingTimer runTimer; + + public TimedTask(RollingTimer runTimer) { + this.runTimer = runTimer; + } + + @Override + public final void run() { + waitTimer.stop(); + + var timer = runTimer.start(); + try { + execute(); + } finally { + timer.stop(); + } + } + + protected abstract void execute(); + } + + private class WriteTask extends TimedTask { private final ChunkCacheKey key; - private final CacheChunkEntry chunk; + private final ChunkCacheEntry chunk; - public WriteTask(ChunkCacheKey key, CacheChunkEntry chunk) { + public WriteTask(ChunkCacheKey key, ChunkCacheEntry chunk) { + super(statistics.diskCacheWriteTime); this.key = key; this.chunk = chunk; } @Override - public void run() { + public void execute() { try { serializer.write(key, chunk); } catch (IOException e) { @@ -159,21 +187,22 @@ public void run() { } } - private class ReadTask implements Runnable { + private class ReadTask extends TimedTask { private final ChunkCacheKey key; - private final CompletableFuture future; + private final CompletableFuture<@Nullable ChunkCacheEntry> future; - public ReadTask(ChunkCacheKey key, CompletableFuture future) { + public ReadTask(ChunkCacheKey key, CompletableFuture<@Nullable ChunkCacheEntry> future) { + super(statistics.diskCacheReadTime); this.key = key; this.future = future; } @Override - public void run() { + public void execute() { try { future.complete(serializer.read(key)); - } catch (IOException e) { + } catch (Exception e) { future.completeExceptionally(e); } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/CacheFileCleanupTask.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheFileCleanupTask.java similarity index 62% rename from orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/CacheFileCleanupTask.java rename to orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheFileCleanupTask.java index 2f68c385..38e59695 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/CacheFileCleanupTask.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheFileCleanupTask.java @@ -1,17 +1,22 @@ -package net.imprex.orebfuscator.cache; +package dev.imprex.orebfuscator.cache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.logging.OfcLogger; import java.io.IOException; +import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileVisitResult; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.logging.OfcLogger; -import net.imprex.orebfuscator.Orebfuscator; - +@NullMarked public class CacheFileCleanupTask implements Runnable { private final CacheConfig cacheConfig; @@ -19,8 +24,8 @@ public class CacheFileCleanupTask implements Runnable { private int deleteCount = 0; - public CacheFileCleanupTask(Orebfuscator orebfuscator, AbstractRegionFileCache regionFileCache) { - this.cacheConfig = orebfuscator.getOrebfuscatorConfig().cache(); + public CacheFileCleanupTask(Config config, AbstractRegionFileCache regionFileCache) { + this.cacheConfig = config.cache(); this.regionFileCache = regionFileCache; } @@ -36,7 +41,7 @@ public void run() { this.deleteCount = 0; try { - Files.walkFileTree(this.cacheConfig.baseDirectory(), new SimpleFileVisitor() { + Files.walkFileTree(this.cacheConfig.baseDirectory(), new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attributes) throws IOException { @@ -45,13 +50,23 @@ public FileVisitResult visitFile(Path path, BasicFileAttributes attributes) thro Files.delete(path); CacheFileCleanupTask.this.deleteCount++; - OfcLogger.debug("deleted cache file: " + path); + OfcLogger.debug("Deleted cache file: " + path); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, @Nullable IOException exc) throws IOException { + try { + Files.delete(dir); + } catch (NoSuchFileException | DirectoryNotEmptyException e) { + // NOOP; we don't care } return FileVisitResult.CONTINUE; } }); } catch (IOException e) { - e.printStackTrace(); + OfcLogger.error(e); } if (this.deleteCount > 0) { diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheRequest.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheRequest.java new file mode 100644 index 00000000..bbaf234e --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheRequest.java @@ -0,0 +1,24 @@ +package dev.imprex.orebfuscator.cache; + +import java.util.Objects; +import org.jspecify.annotations.NullMarked; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import dev.imprex.orebfuscator.util.ChunkCacheKey; + +@NullMarked +public record CacheRequest(ChunkCacheKey cacheKey, byte[] hash) { + + public static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(); + public static final int HASH_LENGTH = HASH_FUNCTION.bits() / Byte.SIZE; + + public CacheRequest { + Objects.requireNonNull(cacheKey); + Objects.requireNonNull(hash); + } + + @Override + public String toString() { + return "CacheRequest [cacheKey=" + cacheKey + "]"; + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheResponse.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheResponse.java new file mode 100644 index 00000000..9518e606 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/CacheResponse.java @@ -0,0 +1,23 @@ +package dev.imprex.orebfuscator.cache; + +import java.util.Objects; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public sealed interface CacheResponse permits CacheResponse.Success, CacheResponse.Failure { + + public static CacheResponse success(ChunkCacheEntry entry) { + return new Success(entry); + } + + record Success(ChunkCacheEntry entry) implements CacheResponse { + + public Success { + Objects.requireNonNull(entry, "entry"); + } + } + + enum Failure implements CacheResponse { + NOT_FOUND, MEMORY_INVALID, DISK_INVALID; + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkCacheEntry.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkCacheEntry.java new file mode 100644 index 00000000..5eeb99b2 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkCacheEntry.java @@ -0,0 +1,91 @@ +package dev.imprex.orebfuscator.cache; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.obfuscation.ObfuscationResponse; +import dev.imprex.orebfuscator.player.ProximityBlock; +import dev.imprex.orebfuscator.util.BlockPos; +import dev.imprex.orebfuscator.util.ChunkCacheKey; +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; + +@NullMarked +public record ChunkCacheEntry(ChunkCacheKey key, byte[] compressedData) { + + public static ChunkCacheEntry create(CacheRequest request, ObfuscationResponse response) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + try (LZ4BlockOutputStream lz4BlockOutputStream = new LZ4BlockOutputStream(byteArrayOutputStream); + DataOutputStream dataOutputStream = new DataOutputStream(lz4BlockOutputStream)) { + + byteArrayOutputStream.write(request.hash()); + + byte[] data = response.data(); + dataOutputStream.writeInt(data.length); + dataOutputStream.write(data, 0, data.length); + + Collection proximityBlocks = response.proximityBlocks(); + dataOutputStream.writeInt(proximityBlocks.size()); + for (ProximityBlock blockPosition : proximityBlocks) { + dataOutputStream.writeInt(blockPosition.blockPos().toSectionPos()); + dataOutputStream.writeByte(blockPosition.flags()); + } + + Collection blockEntities = response.blockEntities(); + dataOutputStream.writeInt(blockEntities.size()); + for (BlockPos blockPosition : blockEntities) { + dataOutputStream.writeInt(blockPosition.toSectionPos()); + } + } catch (Exception e) { + throw new ChunkCacheException("Unable to compress chunk: " + request.cacheKey(), e); + } + + return new ChunkCacheEntry(request.cacheKey(), byteArrayOutputStream.toByteArray()); + } + + public int estimatedSize() { + return 64 + key.world().length() + compressedData.length; + } + + public boolean isValid(CacheRequest request) { + return Arrays.equals(compressedData, 0, CacheRequest.HASH_LENGTH, request.hash(), 0, CacheRequest.HASH_LENGTH); + } + + public ObfuscationResponse toResult() { + try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedData); + LZ4BlockInputStream lz4BlockInputStream = LZ4BlockInputStream.newBuilder().build(byteArrayInputStream); + DataInputStream dataInputStream = new DataInputStream(lz4BlockInputStream)) { + + byteArrayInputStream.skip(CacheRequest.HASH_LENGTH); + + byte[] data = new byte[dataInputStream.readInt()]; + dataInputStream.readFully(data); + + int x = key.x() << 4; + int z = key.z() << 4; + + var proximityBlocks = new ArrayList(); + for (int i = dataInputStream.readInt(); i > 0; i--) { + var blockPos = BlockPos.fromSectionPos(x, z, dataInputStream.readInt()); + var flags = dataInputStream.readByte(); + proximityBlocks.add(new ProximityBlock(blockPos, flags)); + } + + var blockEntities = new HashSet(); + for (int i = dataInputStream.readInt(); i > 0; i--) { + blockEntities.add(BlockPos.fromSectionPos(x, z, dataInputStream.readInt())); + } + + return new ObfuscationResponse(data, blockEntities, proximityBlocks); + } catch (Exception e) { + throw new ChunkCacheException("Unable to decompress chunk: " + key, e); + } + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkCacheException.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkCacheException.java new file mode 100644 index 00000000..ba67bb4c --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkCacheException.java @@ -0,0 +1,12 @@ +package dev.imprex.orebfuscator.cache; + +public class ChunkCacheException extends RuntimeException { + + public ChunkCacheException(String message) { + super(message); + } + + public ChunkCacheException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkSerializer.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkSerializer.java new file mode 100644 index 00000000..465ab488 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ChunkSerializer.java @@ -0,0 +1,64 @@ +package dev.imprex.orebfuscator.cache; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import dev.imprex.orebfuscator.statistics.CacheStatistics; +import dev.imprex.orebfuscator.util.ChunkCacheKey; + +@NullMarked +public class ChunkSerializer { + + private static final int CACHE_VERSION = 3; + + private final AbstractRegionFileCache regionFileCache; + private final CacheStatistics statistics; + + public ChunkSerializer(AbstractRegionFileCache regionFileCache, CacheStatistics statistics) { + this.regionFileCache = regionFileCache; + this.statistics = statistics; + } + + @Nullable + public ChunkCacheEntry read(ChunkCacheKey key) throws IOException { + try (DataInputStream dataInputStream = this.regionFileCache.createInputStream(key)) { + // check if cache entry has right version and if chunk is present + if (dataInputStream == null || dataInputStream.readInt() != CACHE_VERSION || !dataInputStream.readBoolean()) { + statistics.onDiskCacheRead(5); + return null; + } + + byte[] compressedData = new byte[dataInputStream.readInt()]; + dataInputStream.readFully(compressedData); + + statistics.onDiskCacheRead(9 + compressedData.length); + + return new ChunkCacheEntry(key, compressedData); + } catch (IOException e) { + throw new IOException("Unable to read chunk: " + key, e); + } + } + + public void write(ChunkCacheKey key, @Nullable ChunkCacheEntry value) throws IOException { + try (DataOutputStream dataOutputStream = this.regionFileCache.createOutputStream(key)) { + dataOutputStream.writeInt(CACHE_VERSION); + // TODO: merge present boolean (and future flags) into the int32 version field wher int16 for version and int16 for flags + + if (value != null) { + dataOutputStream.writeBoolean(true); + + byte[] compressedData = value.compressedData(); + dataOutputStream.writeInt(compressedData.length); + dataOutputStream.write(compressedData); + statistics.onDiskCacheWrite(9 + compressedData.length); + } else { + dataOutputStream.writeBoolean(false); + statistics.onDiskCacheWrite(5); + } + } catch (IOException e) { + throw new IOException("Unable to write chunk: " + key, e); + } + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ObfuscationCache.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ObfuscationCache.java new file mode 100644 index 00000000..c7e5b07f --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/cache/ObfuscationCache.java @@ -0,0 +1,160 @@ +package dev.imprex.orebfuscator.cache; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalNotification; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.obfuscation.ObfuscationResponse; +import dev.imprex.orebfuscator.statistics.CacheStatistics; +import dev.imprex.orebfuscator.util.ChunkCacheKey; +import dev.imprex.orebfuscator.util.concurrent.OrebfuscatorExecutor; + +@NullMarked +public class ObfuscationCache { + + private final OrebfuscatorCore orebfuscator; + private final CacheConfig cacheConfig; + private final CacheStatistics statistics; + private final OrebfuscatorExecutor executor; + + private final AbstractRegionFileCache regionFileCache; + private final Cache cache; + private final @Nullable AsyncChunkSerializer serializer; + + public ObfuscationCache(OrebfuscatorCore orebfuscator) { + this.orebfuscator = orebfuscator; + this.cacheConfig = orebfuscator.config().cache(); + this.statistics = orebfuscator.statistics().cache; + this.executor = orebfuscator.executor(); + + this.cache = CacheBuilder.newBuilder() + .maximumSize(this.cacheConfig.maximumSize()) + .expireAfterAccess(this.cacheConfig.expireAfterAccess(), TimeUnit.MILLISECONDS) + .removalListener(this::onRemoval) + .build(); + this.statistics.setMemoryCacheEntryCount(this.cache::size); + + this.regionFileCache = orebfuscator.createRegionFileCache(); + + if (this.cacheConfig.enableDiskCache()) { + this.serializer = new AsyncChunkSerializer(orebfuscator, regionFileCache); + } else { + this.serializer = null; + } + + if (this.cacheConfig.enabled() && this.cacheConfig.deleteRegionFilesAfterAccess() > 0) { + var task = new CacheFileCleanupTask(orebfuscator.config(), regionFileCache); + this.executor.scheduleAtFixedRate(task, 0, 1, TimeUnit.HOURS); + } + } + + private void onRemoval(RemovalNotification notification) { + assert notification.getValue() != null; + this.statistics.onCacheSizeChange(-notification.getValue().estimatedSize()); + + // don't serialize invalidated chunks since this would require locking the main + // thread and wouldn't bring a huge improvement + if (this.serializer != null && notification.wasEvicted() && !orebfuscator.isGameThread()) { + assert notification.getKey() != null; + this.serializer.write(notification.getKey(), notification.getValue()); + } + } + + public CompletionStage> get(CacheRequest request) { + return probeCaches(request).thenApplyAsync(response -> { + if (response instanceof CacheResponse.Success success) { + return Optional.of(success.entry().toResult()); + } else { + this.statistics.onCacheMiss(); + return Optional.empty(); + } + }, this.executor).exceptionallyAsync(throwable -> { + OfcLogger.error("An error occurred while trying to get cache entry for request: %s".formatted(request), + throwable); + return Optional.empty(); + }, this.executor); + } + + private CompletionStage probeCaches(CacheRequest request) { + var future = CompletableFuture.supplyAsync(() -> probeMemory(request), this.executor); + + if (this.serializer != null) { + future = future.thenComposeAsync(response -> + // only access disk cache if we couldn't find an entry in memory cache + response == CacheResponse.Failure.NOT_FOUND + ? this.probeDisk(request) + : CompletableFuture.completedStage(response) + , this.executor); + } + + return future; + } + + private CacheResponse probeMemory(CacheRequest request) { + ChunkCacheEntry cacheEntry = this.cache.getIfPresent(request.cacheKey()); + + if (cacheEntry == null) { + return CacheResponse.Failure.NOT_FOUND; + } else if (!cacheEntry.isValid(request)) { + // invalidate invalid in-memory cache entries + this.cache.invalidate(request.cacheKey()); + return CacheResponse.Failure.MEMORY_INVALID; + } + + this.statistics.onCacheHitMemory(); + return CacheResponse.success(cacheEntry); + } + + private CompletionStage probeDisk(CacheRequest request) { + return this.serializer.read(request.cacheKey()).thenApplyAsync(cacheEntry -> { + if (cacheEntry == null) { + return CacheResponse.Failure.NOT_FOUND; + } else if (!cacheEntry.isValid(request)) { + return CacheResponse.Failure.DISK_INVALID; + } + + // add valid disk cache entry to in-memory cache + this.cache.put(request.cacheKey(), cacheEntry); + this.statistics.onCacheSizeChange(cacheEntry.estimatedSize()); + + this.statistics.onCacheHitDisk(); + return CacheResponse.success(cacheEntry); + }, this.executor); + } + + public void add(CacheRequest request, ObfuscationResponse response) { + try { + var entry = ChunkCacheEntry.create(request, response); + this.cache.put(request.cacheKey(), entry); + this.statistics.onCacheSizeChange(entry.estimatedSize()); + } catch (Exception e) { + OfcLogger.error("An error occurred while trying to cache entry for request: %s".formatted(request), e); + } + } + + public void invalidate(ChunkCacheKey key) { + this.cache.invalidate(key); + } + + public void close() { + if (this.serializer != null) { + // flush memory cache to disk on shutdown + this.cache.asMap().entrySet().removeIf(entry -> { + this.serializer.write(entry.getKey(), entry.getValue()); + return true; + }); + + this.serializer.close(); + } + + this.regionFileCache.clear(); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/Chunk.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/Chunk.java index 3c908454..068bf075 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/Chunk.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/Chunk.java @@ -1,8 +1,8 @@ package dev.imprex.orebfuscator.chunk; import java.util.Arrays; -import dev.imprex.orebfuscator.interop.ChunkPacketAccessor; import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.obfuscation.ObfuscationRequest; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.Unpooled; @@ -20,14 +20,15 @@ public class Chunk implements AutoCloseable { private final ByteBuf inputBuffer; private final ByteBuf outputBuffer; - Chunk(ChunkFactory factory, ChunkPacketAccessor packet) { + Chunk(ChunkFactory factory, ObfuscationRequest request) { this.factory = factory; + final var packet = request.packet(); this.chunkX = packet.chunkX(); this.chunkZ = packet.chunkZ(); - this.worldAccessor = packet.world(); - this.sections = new ChunkSectionHolder[this.worldAccessor.getSectionCount()]; + this.worldAccessor = request.world(); + this.sections = new ChunkSectionHolder[this.worldAccessor.sectionCount()]; byte[] data = packet.data(); this.inputBuffer = Unpooled.wrappedBuffer(data); @@ -58,7 +59,7 @@ public ChunkSection getSection(int index) { public int getBlockState(int x, int y, int z) { if (x >> 4 == this.chunkX && z >> 4 == this.chunkZ) { - ChunkSectionHolder chunkSection = this.sections[this.worldAccessor.getSectionIndex(y)]; + ChunkSectionHolder chunkSection = this.sections[this.worldAccessor.sectionIndex(y)]; if (chunkSection != null) { return chunkSection.data[ChunkSection.positionToIndex(x & 0xF, y & 0xF, z & 0xF)]; } @@ -83,7 +84,7 @@ public byte[] finalizeOutput() { } @Override - public void close() throws Exception { + public void close() { this.inputBuffer.release(); this.outputBuffer.release(); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/ChunkFactory.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/ChunkFactory.java index 8581780b..9e718d77 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/ChunkFactory.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/ChunkFactory.java @@ -1,8 +1,8 @@ package dev.imprex.orebfuscator.chunk; -import dev.imprex.orebfuscator.interop.ChunkPacketAccessor; import dev.imprex.orebfuscator.interop.RegistryAccessor; import dev.imprex.orebfuscator.interop.ServerAccessor; +import dev.imprex.orebfuscator.obfuscation.ObfuscationRequest; public class ChunkFactory { @@ -10,7 +10,7 @@ public class ChunkFactory { private final ChunkVersionFlags versionFlags; public ChunkFactory(ServerAccessor serverAccessor) { - this.registryAccessor = serverAccessor.getRegistry(); + this.registryAccessor = serverAccessor.registry(); this.versionFlags = new ChunkVersionFlags(serverAccessor); } @@ -22,7 +22,7 @@ ChunkVersionFlags versionFlags() { return versionFlags; } - public Chunk fromPacket(ChunkPacketAccessor packet) { - return new Chunk(this, packet); + public Chunk fromPacket(ObfuscationRequest request) { + return new Chunk(this, request); } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/ChunkVersionFlags.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/ChunkVersionFlags.java index 14f9cae3..da68a131 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/ChunkVersionFlags.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/chunk/ChunkVersionFlags.java @@ -13,7 +13,7 @@ public final class ChunkVersionFlags { private final boolean hasSingleValuePalette; public ChunkVersionFlags(ServerAccessor serverAccessor) { - var version = serverAccessor.getMinecraftVersion(); + var version = serverAccessor.minecraftVersion(); hasLongArrayLengthField = version.isBelow("1.21.5"); hasBiomePalettedContainer = version.isAtOrAbove("1.18"); hasSingleValuePalette = version.isAtOrAbove("1.18"); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/AbstractWorldConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/AbstractWorldConfig.java index 175fd921..f61acdbd 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/AbstractWorldConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/AbstractWorldConfig.java @@ -1,8 +1,5 @@ package dev.imprex.orebfuscator.config; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; import com.google.gson.JsonObject; import dev.imprex.orebfuscator.config.api.WorldConfig; import dev.imprex.orebfuscator.config.components.BlockParser; @@ -15,9 +12,14 @@ import dev.imprex.orebfuscator.interop.WorldAccessor; import dev.imprex.orebfuscator.logging.OfcLogger; import dev.imprex.orebfuscator.util.BlockPos; -import dev.imprex.orebfuscator.util.MathUtil; +import dev.imprex.orebfuscator.util.QuickMaths; import dev.imprex.orebfuscator.util.WeightedRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.jspecify.annotations.NullMarked; +@NullMarked public abstract class AbstractWorldConfig implements WorldConfig { private final String name; @@ -38,8 +40,8 @@ public AbstractWorldConfig(String name) { protected void deserializeBase(ConfigurationSection section, ConfigParsingContext context) { this.enabledValue = section.getBoolean("enabled", true); - int minY = MathUtil.clamp(section.getInt("minY", BlockPos.MIN_Y), BlockPos.MIN_Y, BlockPos.MAX_Y); - int maxY = MathUtil.clamp(section.getInt("maxY", BlockPos.MAX_Y), BlockPos.MIN_Y, BlockPos.MAX_Y); + int minY = QuickMaths.clamp(section.getInt("minY", BlockPos.MIN_Y), BlockPos.MIN_Y, BlockPos.MAX_Y); + int maxY = QuickMaths.clamp(section.getInt("maxY", BlockPos.MAX_Y), BlockPos.MIN_Y, BlockPos.MAX_Y); this.minY = Math.min(minY, maxY); this.maxY = Math.max(minY, maxY); @@ -74,6 +76,8 @@ protected void deserializeRandomBlocks(BlockParser.Factory blockParserFactory, C return; } + // TODO: validate each height has some valid values or limit height or use some default fill in value + for (ConfigurationSection subSection : subSectionContainer.getSubSections()) { ConfigParsingContext subContext = context.section(subSection.getName()); this.weightedBlockLists.add(new WeightedBlockList(blockParserFactory, subSection, subContext)); @@ -100,12 +104,11 @@ protected String getName() { return name; } - public JsonObject randomBlocksToJson() { - JsonObject object = new JsonObject(); + public void dumpBlocks(ConfigurationSection section) { + var randomBlocks = section.createSection("randomBlocks"); for (WeightedBlockList list : weightedBlockLists) { - object.add(list.getName(), ConfigBlockValue.toJson(list.getBlocks())); + ConfigBlockValue.dump(randomBlocks.createSection(list.getName()), list.getBlocks()); } - return object; } @Override @@ -138,7 +141,6 @@ public boolean shouldObfuscate(int y) { return y >= this.minY && y <= this.maxY; } - WeightedRandom[] createWeightedRandoms(WorldAccessor world) { OfcLogger.debug(String.format("Creating weighted randoms for %s for world %s:", name, world)); return WeightedBlockList.create(world, this.weightedBlockLists); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/ConfigLookup.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/ConfigLookup.java index 3af7a23f..a344ac43 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/ConfigLookup.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/ConfigLookup.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.net.URI; import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -16,12 +17,15 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import dev.imprex.orebfuscator.util.Version; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class ConfigLookup { private static final Pattern FILENAME_PATTERN = Pattern.compile("config-(?.*)\\.yml"); - public static Version getConfigVersion(Version version) throws IOException { + public static @Nullable Version getConfigVersion(Version version) throws IOException { List versions = discoverConfigs().stream() .map(FILENAME_PATTERN::matcher) .filter(Matcher::find) @@ -41,7 +45,7 @@ public static Version getConfigVersion(Version version) throws IOException { return null; } - public static InputStream loadConfig(Version version) { + public static @Nullable InputStream loadConfig(Version version) { String path = String.format("/config/config-%s.yml", version); return ConfigLookup.class.getResourceAsStream(path); } @@ -61,17 +65,12 @@ private static List discoverConfigs() throws IOException { if (location.getPath().endsWith(".jar")) { URI jar = URI.create("jar:" + location); - try (FileSystem fileSystem = FileSystems.newFileSystem(jar, Map.of())) { - Path configDir = fileSystem.getPath("/config/"); - if (!Files.isDirectory(configDir)) { - return Collections.emptyList(); - } - - try (var stream = Files.list(configDir)) { - return stream - .map(configDir::relativize) - .map(Path::toString) - .toList(); + try { + FileSystem fileSystem = FileSystems.getFileSystem(jar); + return discoverConfigs(fileSystem); + } catch (FileSystemNotFoundException e) { + try (var fs = FileSystems.newFileSystem(jar, Map.of())) { + return discoverConfigs(fs); } } } @@ -97,4 +96,18 @@ private static List discoverConfigs() throws IOException { .toList(); } } + + private static List discoverConfigs(FileSystem fileSystem) throws IOException { + Path configDir = fileSystem.getPath("/config/"); + if (!Files.isDirectory(configDir)) { + return Collections.emptyList(); + } + + try (var stream = Files.list(configDir)) { + return stream + .map(configDir::relativize) + .map(Path::toString) + .toList(); + } + } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorAdvancedConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorAdvancedConfig.java index a10a236b..d4283a71 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorAdvancedConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorAdvancedConfig.java @@ -5,40 +5,36 @@ import dev.imprex.orebfuscator.config.context.ConfigParsingContext; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; import dev.imprex.orebfuscator.logging.OfcLogger; +import org.jspecify.annotations.NullMarked; +@NullMarked public class OrebfuscatorAdvancedConfig implements AdvancedConfig { private boolean verbose = false; + private int threads = -1; - private int obfuscationThreads = -1; private long obfuscationTimeout = 10_000L; - private int maxMillisecondsPerTick = 10; - private int proximityThreads = -1; private int proximityDefaultBucketSize = 50; private int proximityThreadCheckInterval = 50; private int proximityPlayerCheckInterval = 5000; - private boolean obfuscationThreadsSet = false; + private boolean hasThreads = false; private boolean hasObfuscationTimeout = false; - private boolean proximityThreadsSet = false; private boolean hasProximityPlayerCheckInterval = true; public void deserialize(ConfigurationSection section, ConfigParsingContext context) { this.verbose = section.getBoolean("verbose", false); + this.threads = section.getInt("threads", -1); + this.hasThreads = (this.threads > 0); + // parse obfuscation section ConfigParsingContext obfuscationContext = context.section("obfuscation"); ConfigurationSection obfuscationSection = section.getSection("obfuscation"); if (obfuscationSection != null) { - this.obfuscationThreads = obfuscationSection.getInt("threads", -1); - this.obfuscationThreadsSet = (this.obfuscationThreads > 0); - this.obfuscationTimeout = obfuscationSection.getLong("timeout", 10_000L); this.hasObfuscationTimeout = (this.obfuscationTimeout > 0); - - this.maxMillisecondsPerTick = obfuscationSection.getInt("maxMillisecondsPerTick", 10); - obfuscationContext.errorMinMaxValue("maxMillisecondsPerTick", 1, 50, this.maxMillisecondsPerTick); } else { obfuscationContext.warn(ConfigMessage.MISSING_USING_DEFAULTS); } @@ -47,9 +43,6 @@ public void deserialize(ConfigurationSection section, ConfigParsingContext conte ConfigParsingContext proximityContext = context.section("proximity"); ConfigurationSection proximitySection = section.getSection("proximity"); if (proximitySection != null) { - this.proximityThreads = proximitySection.getInt("threads", -1); - this.proximityThreadsSet = (this.proximityThreads > 0); - this.proximityDefaultBucketSize = proximitySection.getInt("defaultBucketSize", 50); proximityContext.errorMinValue("defaultBucketSize", 1, this.proximityDefaultBucketSize); @@ -63,22 +56,18 @@ public void deserialize(ConfigurationSection section, ConfigParsingContext conte } int availableThreads = Runtime.getRuntime().availableProcessors(); - this.obfuscationThreads = obfuscationThreadsSet ? obfuscationThreads : availableThreads; - this.proximityThreads = (int) (proximityThreadsSet ? proximityThreads : Math.ceil(availableThreads / 2f)); + this.threads = hasThreads ? threads : availableThreads; OfcLogger.setVerboseLogging(this.verbose); - OfcLogger.debug("advanced.obfuscationThreads = " + this.obfuscationThreads); - OfcLogger.debug("advanced.proximityThreads = " + this.proximityThreads); + OfcLogger.debug("advanced.threads = " + this.threads); } public void serialize(ConfigurationSection section) { section.set("verbose", this.verbose); + section.set("threads", this.hasThreads ? this.threads : -1); - section.set("obfuscation.threads", this.obfuscationThreadsSet ? this.obfuscationThreads : -1); section.set("obfuscation.timeout", this.hasObfuscationTimeout ? this.obfuscationTimeout : -1); - section.set("obfuscation.maxMillisecondsPerTick", this.maxMillisecondsPerTick); - section.set("proximity.threads", this.proximityThreadsSet ? this.proximityThreads : -1); section.set("proximity.defaultBucketSize", this.proximityDefaultBucketSize); section.set("proximity.threadCheckInterval", this.proximityThreadCheckInterval); section.set("proximity.playerCheckInterval", @@ -86,8 +75,8 @@ public void serialize(ConfigurationSection section) { } @Override - public int obfuscationThreads() { - return this.obfuscationThreads; + public int threads() { + return this.threads; } @Override @@ -100,16 +89,6 @@ public long obfuscationTimeout() { return this.obfuscationTimeout; } - @Override - public int maxMillisecondsPerTick() { - return this.maxMillisecondsPerTick; - } - - @Override - public int proximityThreads() { - return this.proximityThreads; - } - @Override public int proximityDefaultBucketSize() { return this.proximityDefaultBucketSize; diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorBlockFlags.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorBlockFlags.java index 81832563..12375797 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorBlockFlags.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorBlockFlags.java @@ -6,15 +6,18 @@ import dev.imprex.orebfuscator.interop.RegistryAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class OrebfuscatorBlockFlags implements BlockFlags { private static final BlockFlags EMPTY_FLAGS = new EmptyBlockFlags(); - static BlockFlags create(RegistryAccessor registry, OrebfuscatorObfuscationConfig worldConfig, - OrebfuscatorProximityConfig proximityConfig) { - if ((worldConfig != null && worldConfig.isEnabled()) || (proximityConfig != null - && proximityConfig.isEnabled())) { + static BlockFlags create(RegistryAccessor registry, + @Nullable OrebfuscatorObfuscationConfig worldConfig, + @Nullable OrebfuscatorProximityConfig proximityConfig) { + if ((worldConfig != null && worldConfig.isEnabled()) || (proximityConfig != null && proximityConfig.isEnabled())) { return new OrebfuscatorBlockFlags(registry, worldConfig, proximityConfig); } return EMPTY_FLAGS; @@ -22,8 +25,9 @@ static BlockFlags create(RegistryAccessor registry, OrebfuscatorObfuscationConfi private final int[] blockFlags; - private OrebfuscatorBlockFlags(RegistryAccessor registry, OrebfuscatorObfuscationConfig worldConfig, - OrebfuscatorProximityConfig proximityConfig) { + private OrebfuscatorBlockFlags(RegistryAccessor registry, + @Nullable OrebfuscatorObfuscationConfig worldConfig, + @Nullable OrebfuscatorProximityConfig proximityConfig) { this.blockFlags = new int[registry.getUniqueBlockStateCount()]; if (worldConfig != null && worldConfig.isEnabled()) { diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorCacheConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorCacheConfig.java index f4b83243..901615ea 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorCacheConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorCacheConfig.java @@ -1,6 +1,7 @@ package dev.imprex.orebfuscator.config; import java.io.IOException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; @@ -13,7 +14,9 @@ import dev.imprex.orebfuscator.interop.ServerAccessor; import dev.imprex.orebfuscator.logging.OfcLogger; import dev.imprex.orebfuscator.util.ChunkCacheKey; +import org.jspecify.annotations.NullMarked; +@NullMarked public class OrebfuscatorCacheConfig implements CacheConfig { private final Path worldDirectory; @@ -33,7 +36,7 @@ public class OrebfuscatorCacheConfig implements CacheConfig { private boolean enableDiskCache = false; public OrebfuscatorCacheConfig(ServerAccessor server) { - this.worldDirectory = server.getWorldDirectory().normalize(); + this.worldDirectory = server.worldDirectory().normalize(); this.baseDirectory = this.worldDirectory.resolve("orebfuscator_cache/"); } @@ -145,7 +148,8 @@ public Path baseDirectory() { @Override public Path regionFile(ChunkCacheKey key) { - return this.baseDirectory.resolve(key.world()) + var separator = this.baseDirectory.getFileSystem().getSeparator(); + return this.baseDirectory.resolve(key.world().replace(":", separator)) .resolve("r." + (key.x() >> 5) + "." + (key.z() >> 5) + ".mca"); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorConfig.java index 6c2ca44b..53789ec5 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorConfig.java @@ -13,8 +13,10 @@ import java.util.WeakHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.random.RandomGenerator; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import com.google.common.hash.Hashing; -import com.google.gson.JsonObject; import dev.imprex.orebfuscator.config.api.AdvancedConfig; import dev.imprex.orebfuscator.config.api.BlockFlags; import dev.imprex.orebfuscator.config.api.CacheConfig; @@ -38,9 +40,10 @@ import dev.imprex.orebfuscator.util.Version; import dev.imprex.orebfuscator.util.WeightedRandom; +@NullMarked public class OrebfuscatorConfig implements Config { - private static final int CONFIG_VERSION = 5; + private static final int CONFIG_VERSION = 6; private final OrebfuscatorGeneralConfig generalConfig = new OrebfuscatorGeneralConfig(); private final OrebfuscatorAdvancedConfig advancedConfig = new OrebfuscatorAdvancedConfig(); @@ -49,19 +52,19 @@ public class OrebfuscatorConfig implements Config { private final List obfuscationConfigs = new ArrayList<>(); private final List proximityConfigs = new ArrayList<>(); - private final Map worldConfigBundles = new WeakHashMap<>(); + private final Map worldConfigBundles = new WeakHashMap<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final ServerAccessor server; private final Path path; private final YamlConfiguration configuration; - private byte[] systemHash; - private String configReport; + private byte[] systemHash = new byte[0]; + private @Nullable String configReport; public OrebfuscatorConfig(ServerAccessor server) { this.server = server; - this.path = server.getConfigDirectory().resolve("config.yml"); + this.path = server.configDirectory().resolve("config.yml"); this.cacheConfig = new OrebfuscatorCacheConfig(this.server); this.configuration = this.loadConfiguration(); @@ -73,8 +76,12 @@ public YamlConfiguration loadConfiguration() { if (Files.notExists(this.path)) { Files.createDirectories(this.path.getParent()); - Version version = this.server.getMinecraftVersion(); + Version version = this.server.minecraftVersion(); Version configVersion = ConfigLookup.getConfigVersion(version); + if (configVersion == null) { + throw new InvalidConfigurationException( + "No config found and can't find default config for your version"); + } OfcLogger.info( String.format("No config found, creating default config for version %s and above", configVersion)); @@ -91,12 +98,14 @@ public YamlConfiguration loadConfiguration() { this.deserialize(configuration, context); this.configReport = context.report(); - if (context.hasErrors()) { - OfcLogger.error(this.configReport, null); - throw new IllegalArgumentException( - "Can't parse config due to errors, Orebfuscator will now disable itself!"); - } else if (this.configReport != null) { - OfcLogger.warn(this.configReport); + if (this.configReport != null) { + if (context.hasErrors()) { + OfcLogger.error(this.configReport, null); + throw new InvalidConfigurationException( + "Can't parse config due to errors, Orebfuscator will now disable itself!"); + } else { + OfcLogger.warn(this.configReport); + } } this.systemHash = this.calculateSystemHash(configuration); @@ -120,8 +129,8 @@ public void store() { private byte[] calculateSystemHash(YamlConfiguration configuration) throws IOException { return Hashing.murmur3_128().newHasher() - .putBytes(this.server.getOrebfuscatorVersion().getBytes(StandardCharsets.UTF_8)) - .putBytes(this.server.getMinecraftVersion().toString().getBytes(StandardCharsets.UTF_8)) + .putBytes(this.server.orebfuscatorVersion().toString().getBytes(StandardCharsets.UTF_8)) + .putBytes(this.server.minecraftVersion().toString().getBytes(StandardCharsets.UTF_8)) .putBytes(configuration.withoutComments().getBytes(StandardCharsets.UTF_8)) .hash().asBytes(); } @@ -129,7 +138,7 @@ private byte[] calculateSystemHash(YamlConfiguration configuration) throws IOExc private void deserialize(YamlConfiguration configuration, ConfigParsingContext context) { if (ConfigMigrator.willMigrate(configuration)) { try { - configuration.save(server.getConfigDirectory().resolve("config-old.yml")); + configuration.save(server.configDirectory().resolve("config-old.yml")); } catch (IOException e) { OfcLogger.error("Can't save original config before migration", e); } @@ -174,7 +183,7 @@ private void deserialize(YamlConfiguration configuration, ConfigParsingContext c cacheContext.warn(ConfigMessage.MISSING_USING_DEFAULTS); } - final BlockParser.Factory blockParserFactory = BlockParser.factory(server.getRegistry()); + final BlockParser.Factory blockParserFactory = BlockParser.factory(server.registry()); // parse obfuscation sections ConfigParsingContext obfuscationContext = context.section("obfuscation"); @@ -203,7 +212,7 @@ private void deserialize(YamlConfiguration configuration, ConfigParsingContext c proximityContext.warn(ConfigMessage.MISSING_OR_EMPTY); } - for (WorldAccessor world : this.server.getWorlds()) { + for (WorldAccessor world : this.server.worlds()) { this.worldConfigBundles.put(world, new OrebfuscatorWorldConfigBundle(world)); } } @@ -226,18 +235,16 @@ private void serialize(ConfigurationSection section) { } } - public JsonObject toJson() { - JsonObject object = new JsonObject(); - + public void dumpBlocks(ConfigurationSection section) { + var obfuscation = section.createSection("obfuscation"); for (var config : obfuscationConfigs) { - object.add(config.getName(), config.toJson()); + config.dumpBlocks(obfuscation.createSection(config.getName())); } + var proximity = section.createSection("proximity"); for (var config : proximityConfigs) { - object.add(config.getName(), config.toJson()); + config.dumpBlocks(proximity.createSection(config.getName())); } - - return object; } @Override @@ -246,7 +253,7 @@ public byte[] systemHash() { } @Override - public String report() { + public @Nullable String report() { return configReport; } @@ -330,8 +337,8 @@ private OrebfuscatorWorldConfigBundle getWorldConfigBundle(WorldAccessor world) private class OrebfuscatorWorldConfigBundle implements WorldConfigBundle { - private final OrebfuscatorObfuscationConfig obfuscationConfig; - private final OrebfuscatorProximityConfig proximityConfig; + private final @Nullable OrebfuscatorObfuscationConfig obfuscationConfig; + private final @Nullable OrebfuscatorProximityConfig proximityConfig; private final BlockFlags blockFlags; private final boolean needsObfuscation; @@ -343,17 +350,17 @@ private class OrebfuscatorWorldConfigBundle implements WorldConfigBundle { private final int maxSectionIndex; private final WorldAccessor world; - private final WeightedRandom[] obfuscationRandoms; - private final WeightedRandom[] proximityRandoms; + private final WeightedRandom @Nullable [] obfuscationRandoms; + private final WeightedRandom @Nullable [] proximityRandoms; public OrebfuscatorWorldConfigBundle(WorldAccessor world) { - String worldName = world.getName(); + String worldName = world.name(); this.world = world; this.obfuscationConfig = findConfig(obfuscationConfigs, worldName, "obfuscation"); this.proximityConfig = findConfig(proximityConfigs, worldName, "proximity"); - this.blockFlags = OrebfuscatorBlockFlags.create(server.getRegistry(), obfuscationConfig, proximityConfig); + this.blockFlags = OrebfuscatorBlockFlags.create(server.registry(), obfuscationConfig, proximityConfig); this.needsObfuscation = obfuscationConfig != null && obfuscationConfig.isEnabled() || proximityConfig != null && proximityConfig.isEnabled(); @@ -364,16 +371,16 @@ public OrebfuscatorWorldConfigBundle(WorldAccessor world) { this.obfuscationConfig != null ? this.obfuscationConfig.getMaxY() : BlockPos.MIN_Y, this.proximityConfig != null ? this.proximityConfig.getMaxY() : BlockPos.MIN_Y); - this.minSectionIndex = world.getSectionIndex(this.minY); - this.maxSectionIndex = world.getSectionIndex(this.maxY - 1) + 1; + this.minSectionIndex = world.sectionIndex(this.minY); + this.maxSectionIndex = world.sectionIndex(this.maxY - 1) + 1; - this.obfuscationRandoms = this.obfuscationConfig != null + this.obfuscationRandoms = obfuscationConfig != null && obfuscationConfig.isEnabled() ? this.obfuscationConfig.createWeightedRandoms(world) : null; - this.proximityRandoms = this.proximityConfig != null + this.proximityRandoms = proximityConfig != null && proximityConfig.isEnabled() ? this.proximityConfig.createWeightedRandoms(world) : null; } - private T findConfig(Collection configs, String worldName, + private @Nullable T findConfig(Collection configs, String worldName, String configType) { List matchingConfigs = configs.stream() .filter(config -> config.matchesWorldName(worldName)) @@ -398,12 +405,12 @@ public BlockFlags blockFlags() { } @Override - public ObfuscationConfig obfuscation() { + public @Nullable ObfuscationConfig obfuscation() { return this.obfuscationConfig; } @Override - public ProximityConfig proximity() { + public @Nullable ProximityConfig proximity() { return this.proximityConfig; } @@ -428,15 +435,15 @@ public boolean shouldObfuscate(int y) { } @Override - public int nextRandomObfuscationBlock(int y) { + public int nextRandomObfuscationBlock(RandomGenerator random, int y) { return this.obfuscationRandoms != null - ? this.obfuscationRandoms[y - this.world.getMinBuildHeight()].next() : 0; + ? this.obfuscationRandoms[y - this.world.minBuildHeight()].next(random) : 0; } @Override - public int nextRandomProximityBlock(int y) { + public int nextRandomProximityBlock(RandomGenerator random, int y) { return this.proximityRandoms != null - ? this.proximityRandoms[y - this.world.getMinBuildHeight()].next() : 0; + ? this.proximityRandoms[y - this.world.minBuildHeight()].next(random) : 0; } } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorGeneralConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorGeneralConfig.java index 4dea6850..647a950d 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorGeneralConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorGeneralConfig.java @@ -3,7 +3,9 @@ import dev.imprex.orebfuscator.config.api.GeneralConfig; import dev.imprex.orebfuscator.config.context.ConfigParsingContext; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; +import org.jspecify.annotations.NullMarked; +@NullMarked public class OrebfuscatorGeneralConfig implements GeneralConfig { private boolean checkForUpdates = true; diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorObfuscationConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorObfuscationConfig.java index e1dc29b1..d4bae980 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorObfuscationConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorObfuscationConfig.java @@ -1,5 +1,6 @@ package dev.imprex.orebfuscator.config; +import dev.imprex.orebfuscator.config.components.WeightedBlockList; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; @@ -11,7 +12,9 @@ import dev.imprex.orebfuscator.config.context.ConfigMessage; import dev.imprex.orebfuscator.config.context.ConfigParsingContext; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; +import org.jspecify.annotations.NullMarked; +@NullMarked public class OrebfuscatorObfuscationConfig extends AbstractWorldConfig implements ObfuscationConfig { private boolean layerObfuscation = false; @@ -67,11 +70,9 @@ private void serializeHiddenBlocks(ConfigurationSection section) { section.set("hiddenBlocks", blockNames); } - public JsonObject toJson() { - JsonObject object = new JsonObject(); - object.add("hiddenBlocks", ConfigBlockValue.toJson(hiddenBlocks)); - object.add("randomBlocks", randomBlocksToJson()); - return object; + public void dumpBlocks(ConfigurationSection section) { + ConfigBlockValue.dump(section.createSection("hiddenBlocks"), hiddenBlocks); + super.dumpBlocks(section); } @Override diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorProximityConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorProximityConfig.java index 509c8b7a..edbb71d1 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorProximityConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/OrebfuscatorProximityConfig.java @@ -15,7 +15,9 @@ import dev.imprex.orebfuscator.config.context.ConfigParsingContext; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; import dev.imprex.orebfuscator.util.BlockProperties; +import org.jspecify.annotations.NullMarked; +@NullMarked public class OrebfuscatorProximityConfig extends AbstractWorldConfig implements ProximityConfig { private int distance = 24; @@ -159,11 +161,9 @@ private void serializeHiddenBlocks(ConfigurationSection section) { } } - public JsonObject toJson() { - JsonObject object = new JsonObject(); - object.add("hiddenBlocks", ConfigBlockValue.toJson(hiddenBlocks.keySet())); - object.add("randomBlocks", randomBlocksToJson()); - return object; + public void dumpBlocks(ConfigurationSection section) { + ConfigBlockValue.dump(section.createSection("hiddenBlocks"), hiddenBlocks.keySet()); + super.dumpBlocks(section); } @Override diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/AdvancedConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/AdvancedConfig.java index 93427ce5..b559a1d2 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/AdvancedConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/AdvancedConfig.java @@ -1,17 +1,16 @@ package dev.imprex.orebfuscator.config.api; +import org.jspecify.annotations.NullMarked; + +@NullMarked public interface AdvancedConfig { - int obfuscationThreads(); + int threads(); boolean hasObfuscationTimeout(); long obfuscationTimeout(); - int maxMillisecondsPerTick(); - - int proximityThreads(); - int proximityDefaultBucketSize(); int proximityThreadCheckInterval(); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/BlockFlags.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/BlockFlags.java index 98e36a54..76db1352 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/BlockFlags.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/BlockFlags.java @@ -1,5 +1,8 @@ package dev.imprex.orebfuscator.config.api; +import org.jspecify.annotations.NullMarked; + +@NullMarked public interface BlockFlags { int FLAG_OBFUSCATE = 1; diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/CacheConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/CacheConfig.java index ae4569e7..b3c7ee57 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/CacheConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/CacheConfig.java @@ -2,7 +2,9 @@ import java.nio.file.Path; import dev.imprex.orebfuscator.util.ChunkCacheKey; +import org.jspecify.annotations.NullMarked; +@NullMarked public interface CacheConfig { boolean enabled(); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/Config.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/Config.java index 5cd05f26..e3544330 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/Config.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/Config.java @@ -1,12 +1,15 @@ package dev.imprex.orebfuscator.config.api; import dev.imprex.orebfuscator.interop.WorldAccessor; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public interface Config { byte[] systemHash(); - String report(); + @Nullable String report(); GeneralConfig general(); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/GeneralConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/GeneralConfig.java index 96a5972c..7f50c251 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/GeneralConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/GeneralConfig.java @@ -1,5 +1,8 @@ package dev.imprex.orebfuscator.config.api; +import org.jspecify.annotations.NullMarked; + +@NullMarked public interface GeneralConfig { boolean checkForUpdates(); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/ObfuscationConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/ObfuscationConfig.java index ddb22cff..a8dd2a62 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/ObfuscationConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/ObfuscationConfig.java @@ -1,7 +1,9 @@ package dev.imprex.orebfuscator.config.api; import dev.imprex.orebfuscator.config.components.ConfigBlockValue; +import org.jspecify.annotations.NullMarked; +@NullMarked public interface ObfuscationConfig extends WorldConfig { boolean layerObfuscation(); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/ProximityConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/ProximityConfig.java index b529f4e0..ec31fa0e 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/ProximityConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/ProximityConfig.java @@ -5,7 +5,9 @@ import org.joml.Matrix4f; import dev.imprex.orebfuscator.config.components.ConfigBlockValue; +import org.jspecify.annotations.NullMarked; +@NullMarked public interface ProximityConfig extends WorldConfig { int distance(); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/WorldConfig.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/WorldConfig.java index 8b19b235..f0b3e2a0 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/WorldConfig.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/WorldConfig.java @@ -1,5 +1,8 @@ package dev.imprex.orebfuscator.config.api; +import org.jspecify.annotations.NullMarked; + +@NullMarked public interface WorldConfig { boolean isEnabled(); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/WorldConfigBundle.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/WorldConfigBundle.java index ba33f936..48340427 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/WorldConfigBundle.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/api/WorldConfigBundle.java @@ -1,12 +1,17 @@ package dev.imprex.orebfuscator.config.api; +import java.util.random.RandomGenerator; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked public interface WorldConfigBundle { BlockFlags blockFlags(); - ObfuscationConfig obfuscation(); + @Nullable ObfuscationConfig obfuscation(); - ProximityConfig proximity(); + @Nullable ProximityConfig proximity(); boolean needsObfuscation(); @@ -16,7 +21,7 @@ public interface WorldConfigBundle { boolean shouldObfuscate(int y); - int nextRandomObfuscationBlock(int y); + int nextRandomObfuscationBlock(RandomGenerator random, int y); - int nextRandomProximityBlock(int y); + int nextRandomProximityBlock(RandomGenerator random, int y); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/BlockParser.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/BlockParser.java index 92743c63..f199392a 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/BlockParser.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/BlockParser.java @@ -3,29 +3,29 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; -import org.jetbrains.annotations.NotNull; import dev.imprex.orebfuscator.config.context.ConfigMessage; import dev.imprex.orebfuscator.config.context.ConfigParsingContext; import dev.imprex.orebfuscator.interop.RegistryAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockTag; +import org.jspecify.annotations.NullMarked; +@NullMarked public class BlockParser { - public static BlockParser.Factory factory(RegistryAccessor registryAccessor) { + public static Factory factory(RegistryAccessor registryAccessor) { return new Factory(registryAccessor); } - private final @NotNull RegistryAccessor registry; + private final RegistryAccessor registry; private final boolean excludeAir; - private BlockParser(@NotNull RegistryAccessor registry, boolean excludeAir) { + private BlockParser(RegistryAccessor registry, boolean excludeAir) { this.registry = Objects.requireNonNull(registry); this.excludeAir = excludeAir; } - @NotNull - public ConfigBlockValue parse(@NotNull ConfigParsingContext context, @NotNull String value) { + public ConfigBlockValue parse(ConfigParsingContext context, String value) { var parsed = ConfigFunctionValue.parse(value); if (parsed != null) { return switch (parsed.function()) { @@ -40,8 +40,7 @@ public ConfigBlockValue parse(@NotNull ConfigParsingContext context, @NotNull St } } - @NotNull - private ConfigBlockValue parseBlockTag(@NotNull ConfigParsingContext context, @NotNull String value) { + private ConfigBlockValue parseBlockTag(ConfigParsingContext context, String value) { BlockTag tag = registry.getBlockTagByName(value); if (tag == null) { context.warn(ConfigMessage.BLOCK_TAG_UNKNOWN, value); @@ -76,8 +75,7 @@ private ConfigBlockValue parseBlockTag(@NotNull ConfigParsingContext context, @N return ConfigBlockValue.tag(tag, blocks); } - @NotNull - private ConfigBlockValue parseBlock(@NotNull ConfigParsingContext context, @NotNull String value) { + private ConfigBlockValue parseBlock(ConfigParsingContext context, String value) { BlockProperties block = registry.getBlockByName(value); if (block == null) { context.warn(ConfigMessage.BLOCK_UNKNOWN, value); @@ -92,14 +90,10 @@ private ConfigBlockValue parseBlock(@NotNull ConfigParsingContext context, @NotN public static class Factory { - private final @NotNull RegistryAccessor registry; - private final BlockParser excludeAir; private final BlockParser includeAir; - public Factory(@NotNull RegistryAccessor registry) { - this.registry = Objects.requireNonNull(registry); - + public Factory(RegistryAccessor registry) { this.excludeAir = new BlockParser(registry, true); this.includeAir = new BlockParser(registry, false); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/ConfigBlockValue.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/ConfigBlockValue.java index 4bee4daf..8a411dfe 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/ConfigBlockValue.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/ConfigBlockValue.java @@ -1,62 +1,52 @@ package dev.imprex.orebfuscator.config.components; +import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; import java.util.Collection; import java.util.Collections; import java.util.Objects; import java.util.Set; -import org.jetbrains.annotations.NotNull; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockTag; +import org.jspecify.annotations.NullMarked; -public record ConfigBlockValue(@NotNull String value, @NotNull Set blocks) implements +@NullMarked +public record ConfigBlockValue(String value, Set blocks) implements Comparable { private static final JsonElement INVALID = new JsonPrimitive("invalid"); private static final JsonElement VALID = new JsonPrimitive("valid"); - - public static JsonObject toJson(Collection values) { - JsonObject object = new JsonObject(); - var list = values.stream().sorted().toList(); - - for (var entry : list) { + public static void dump(ConfigurationSection section, Collection values) { + for (var entry : values.stream().sorted().toList()) { if (entry.blocks().size() > 1) { - JsonArray array = new JsonArray(entry.blocks().size()); - for (var block : entry.blocks()) { - array.add(block.getKey().toString()); - } - object.add(entry.value(), array); - } else if (entry.blocks().size() > 0) { - object.add(entry.value(), VALID); + section.set(entry.value(), entry.blocks().stream() + .map(block -> block.getKey().toString()) + .toList()); + } else if (!entry.blocks().isEmpty()) { + section.set(entry.value(), VALID); } else { - object.add(entry.value(), INVALID); + section.set(entry.value(), INVALID); } } - - return object; } - @NotNull - public static ConfigBlockValue invalid(@NotNull String value) { + public static ConfigBlockValue invalid(String value) { return new ConfigBlockValue(value, Collections.emptySet()); } - @NotNull - public static ConfigBlockValue block(@NotNull BlockProperties block) { + public static ConfigBlockValue block(BlockProperties block) { return new ConfigBlockValue(block.getKey().toString(), Set.of(block)); } - @NotNull - public static ConfigBlockValue invalidTag(@NotNull String value) { + public static ConfigBlockValue invalidTag(String value) { return new ConfigBlockValue(String.format("tag(%s)", value), Collections.emptySet()); } - @NotNull - public static ConfigBlockValue tag(@NotNull BlockTag tag, @NotNull Set blocks) { + public static ConfigBlockValue tag(BlockTag tag, Set blocks) { return new ConfigBlockValue(String.format("tag(%s)", tag.key()), Collections.unmodifiableSet(blocks)); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/ConfigFunctionValue.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/ConfigFunctionValue.java index 9f456bf7..dde3c4fc 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/ConfigFunctionValue.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/ConfigFunctionValue.java @@ -2,15 +2,15 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -public record ConfigFunctionValue(@NotNull String function, @NotNull String argument) { +@NullMarked +public record ConfigFunctionValue(String function, String argument) { private static final Pattern CONFIG_FUNCTION_PATTERN = Pattern.compile("^(?\\w+)\\((?.+)\\)$"); - @Nullable - public static ConfigFunctionValue parse(@NotNull String value) { + public static @Nullable ConfigFunctionValue parse(String value) { Matcher matcher = CONFIG_FUNCTION_PATTERN.matcher(value); if (matcher.find()) { String function = matcher.group("function"); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/WeightedBlockList.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/WeightedBlockList.java index 8973f914..3085db7c 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/WeightedBlockList.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/WeightedBlockList.java @@ -1,5 +1,6 @@ package dev.imprex.orebfuscator.config.components; +import dev.imprex.orebfuscator.util.BlockProperties; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -14,27 +15,29 @@ import dev.imprex.orebfuscator.logging.OfcLogger; import dev.imprex.orebfuscator.util.BlockPos; import dev.imprex.orebfuscator.util.BlockStateProperties; -import dev.imprex.orebfuscator.util.MathUtil; +import dev.imprex.orebfuscator.util.QuickMaths; import dev.imprex.orebfuscator.util.WeightedRandom; +import org.jspecify.annotations.NullMarked; +@NullMarked public class WeightedBlockList { public static WeightedRandom[] create(WorldAccessor world, List lists) { - WeightedRandom[] heightMap = new WeightedRandom[world.getHeight()]; + WeightedRandom[] heightMap = new WeightedRandom[world.height()]; List last = new ArrayList<>(); List next = new ArrayList<>(); int count = 0; - for (int y = world.getMinBuildHeight(); y < world.getMaxBuildHeight(); y++) { + for (int y = world.minBuildHeight(); y < world.maxBuildHeight(); y++) { for (WeightedBlockList list : lists) { if (list.minY <= y && list.maxY >= y) { next.add(list); } } - int index = y - world.getMinBuildHeight(); + int index = y - world.minBuildHeight(); if (index > 0 && last.equals(next)) { // copy last weighted random heightMap[index] = heightMap[index - 1]; @@ -45,7 +48,7 @@ public static WeightedRandom[] create(WorldAccessor world, List entry : list.blocks.entrySet()) { // TODO: add support for other block states in future var blockStates = entry.getKey().blocks().stream() - .map(block -> block.getDefaultBlockState()) + .map(BlockProperties::getDefaultBlockState) .collect(Collectors.toSet()); double weight = (double) entry.getValue() / (double) blockStates.size(); @@ -82,8 +85,8 @@ public WeightedBlockList(BlockParser.Factory blockParserFactory, ConfigurationSe ConfigParsingContext context) { this.name = section.getName(); - int minY = MathUtil.clamp(section.getInt("minY", BlockPos.MIN_Y), BlockPos.MIN_Y, BlockPos.MAX_Y); - int maxY = MathUtil.clamp(section.getInt("maxY", BlockPos.MAX_Y), BlockPos.MIN_Y, BlockPos.MAX_Y); + int minY = QuickMaths.clamp(section.getInt("minY", BlockPos.MIN_Y), BlockPos.MIN_Y, BlockPos.MAX_Y); + int maxY = QuickMaths.clamp(section.getInt("maxY", BlockPos.MAX_Y), BlockPos.MIN_Y, BlockPos.MAX_Y); this.minY = Math.min(minY, maxY); this.maxY = Math.max(minY, maxY); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/WorldMatcher.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/WorldMatcher.java index 40beacd3..be3b4dd4 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/WorldMatcher.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/components/WorldMatcher.java @@ -2,12 +2,12 @@ import java.util.function.Predicate; import java.util.regex.Pattern; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; +@NullMarked public class WorldMatcher implements Predicate { - @NotNull - public static WorldMatcher parseMatcher(@NotNull String value) { + public static WorldMatcher parseMatcher(String value) { var parsed = ConfigFunctionValue.parse(value); if (parsed != null && parsed.function().equals("regex")) { return new WorldMatcher(parseRegexMatcher(parsed.argument()), Type.REGEX); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/ConfigMessage.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/ConfigMessage.java index 2aa95355..8dcc4a71 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/ConfigMessage.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/ConfigMessage.java @@ -1,9 +1,10 @@ package dev.imprex.orebfuscator.config.context; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class ConfigMessage { public static final ConfigMessage MISSING_OR_EMPTY = fixed("is missing or empty"); @@ -30,25 +31,24 @@ public class ConfigMessage { public static final ConfigMessage BLOCK_UNKNOWN = dynamic("skipping unknown block '%s'"); public static final ConfigMessage BLOCK_AIR = dynamic("skipping air block '%s'"); - private static ConfigMessage fixed(@NotNull String message) { + private static ConfigMessage fixed(String message) { return new ConfigMessage(message, true); } - private static ConfigMessage dynamic(@NotNull String message) { + private static ConfigMessage dynamic(String message) { return new ConfigMessage(message, false); } private final String message; private final boolean fixed; - private ConfigMessage(@NotNull String message, boolean fixed) { + private ConfigMessage(String message, boolean fixed) { this.message = message; this.fixed = fixed; } - @NotNull @Contract(pure = true) - public String format(@Nullable Object... arguments) { + public String format(Object @Nullable ... arguments) { return this.fixed ? this.message : String.format(this.message, arguments); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/ConfigParsingContext.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/ConfigParsingContext.java index 74f8a8cb..30164ec4 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/ConfigParsingContext.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/ConfigParsingContext.java @@ -1,8 +1,8 @@ package dev.imprex.orebfuscator.config.context; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Represents a context used during configuration parsing to track and collect warnings and errors. @@ -16,6 +16,7 @@ * individually without affecting the entire configuration. *

*/ +@NullMarked public interface ConfigParsingContext { /** @@ -25,8 +26,7 @@ public interface ConfigParsingContext { * @param isolateErrors Whether errors in this child context should be isolated from the parent. * @return An existing or newly created {@code ConfigParsingContext} instance for the given path. */ - @NotNull - ConfigParsingContext section(@NotNull String path, boolean isolateErrors); + ConfigParsingContext section(String path, boolean isolateErrors); /** * Creates or retrieves an existing child context for the specified path without isolating errors. @@ -34,8 +34,7 @@ public interface ConfigParsingContext { * @param path The relative path of the child context. * @return An existing or newly created {@code ConfigParsingContext} instance for the given path. */ - @NotNull - default ConfigParsingContext section(@NotNull String path) { + default ConfigParsingContext section(String path) { return section(path, false); } @@ -45,7 +44,7 @@ default ConfigParsingContext section(@NotNull String path) { * @param message The warning message to be logged. * @param arguments Optional arguments used to format the message. */ - void warn(@NotNull ConfigMessage message, @Nullable Object... arguments); + void warn(ConfigMessage message, Object @Nullable ... arguments); /** * Adds a warning message to the context of the specified path. @@ -54,7 +53,7 @@ default ConfigParsingContext section(@NotNull String path) { * @param message The warning message to be logged. * @param arguments Optional arguments used to format the message. */ - void warn(@NotNull String path, @NotNull ConfigMessage message, @Nullable Object... arguments); + void warn(String path, ConfigMessage message, Object @Nullable ... arguments); /** * Determines whether a subsystem should remain enabled based on the presence of errors. @@ -82,7 +81,7 @@ default boolean disableIfError(boolean enabled) { * @param message The error message to be logged. * @param arguments Optional arguments used to format the message. */ - void error(@NotNull ConfigMessage message, @Nullable Object... arguments); + void error(ConfigMessage message, Object @Nullable ... arguments); /** * Adds an error message to the context of the specified path. @@ -91,7 +90,7 @@ default boolean disableIfError(boolean enabled) { * @param message The error message to be logged. * @param arguments Optional arguments used to format the message. */ - void error(@NotNull String path, @NotNull ConfigMessage message, @Nullable Object... arguments); + void error(String path, ConfigMessage message, Object @Nullable ... arguments); /** * Adds an error if the specified value is below the minimum allowed value. @@ -100,7 +99,7 @@ default boolean disableIfError(boolean enabled) { * @param min The minimum allowed value. * @param value The actual value to be checked. */ - default void errorMinValue(@NotNull String path, long min, long value) { + default void errorMinValue(String path, long min, long value) { if (value < min) { error(path, ConfigMessage.VALUE_MIN, value, min); } @@ -114,7 +113,7 @@ default void errorMinValue(@NotNull String path, long min, long value) { * @param max The maximum allowed value. * @param value The actual value to be checked. */ - default void errorMinMaxValue(@NotNull String path, long min, long max, long value) { + default void errorMinMaxValue(String path, long min, long max, long value) { if (value < min || value > max) { error(path, ConfigMessage.VALUE_MIN_MAX, value, min, max); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/DefaultConfigParsingContext.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/DefaultConfigParsingContext.java index b8404140..2ef1573a 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/DefaultConfigParsingContext.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/context/DefaultConfigParsingContext.java @@ -6,11 +6,11 @@ import java.util.List; import java.util.Map; import java.util.Objects; - import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +@NullMarked public class DefaultConfigParsingContext implements ConfigParsingContext { private static final String ANSI_RESET = "\u001B[m"; @@ -34,34 +34,33 @@ private DefaultConfigParsingContext(@Nullable DefaultConfigParsingContext parent } @Override - @NotNull - public DefaultConfigParsingContext section(@NotNull String path, boolean isolateErrors) { + public DefaultConfigParsingContext section(String path, boolean isolateErrors) { DefaultConfigParsingContext context = getContext(path); context.isolateErrors = isolateErrors; return context; } @Override - public void warn(@NotNull ConfigMessage message, @Nullable Object... arguments) { + public void warn(ConfigMessage message, Object @Nullable ... arguments) { Objects.requireNonNull(message, "message can't be null"); this.messages.add(new Message(false, message.format(arguments))); } @Override - public void warn(@NotNull String path, @NotNull ConfigMessage message, @Nullable Object... arguments) { + public void warn(String path, ConfigMessage message, Object @Nullable ... arguments) { getContext(path).warn(message, arguments); } @Override - public void error(@NotNull ConfigMessage message, @Nullable Object... arguments) { + public void error(ConfigMessage message, Object @Nullable ... arguments) { Objects.requireNonNull(message, "message can't be null"); this.messages.add(new Message(true, message.format(arguments))); } @Override - public void error(@NotNull String path, @NotNull ConfigMessage message, @Nullable Object... arguments) { + public void error(String path, ConfigMessage message, Object @Nullable ... arguments) { getContext(path).error(message, arguments); } @@ -83,7 +82,7 @@ public boolean hasErrors() { return false; } - private DefaultConfigParsingContext getContext(@NotNull String path) { + private DefaultConfigParsingContext getContext(String path) { Objects.requireNonNull(path, "context path can't be null"); DefaultConfigParsingContext context = this; @@ -136,8 +135,7 @@ private StringBuilder buildReport(final StringBuilder builder) { return builder; } - @Nullable - public String report() { + public @Nullable String report() { int messageCount = this.getMessageCount(); if (messageCount == 0) { return null; @@ -153,7 +151,7 @@ public String report() { .toString(); } - private record Message(boolean isError, @NotNull String content) implements Comparable { + private record Message(boolean isError, String content) implements Comparable { @Override public int compareTo(Message o) { diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigration.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigration.java index d455e41b..3c02c110 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigration.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigration.java @@ -4,18 +4,19 @@ import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked interface ConfigMigration { int sourceVersion(); - @NotNull ConfigurationSection migrate(@NotNull ConfigurationSection root); + ConfigurationSection migrate(ConfigurationSection root); - static void migrateNames(@Nullable ConfigurationSection section, @NotNull List> mapping) { + static void migrateNames(@Nullable ConfigurationSection section, List> mapping) { Objects.requireNonNull(mapping, "mappings can't be null"); if (section == null) { return; diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV1.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV1.java index e752b16f..f8565d91 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV1.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV1.java @@ -5,10 +5,11 @@ import java.util.List; import java.util.Map; -import org.jetbrains.annotations.NotNull; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; +import org.jspecify.annotations.NullMarked; +@NullMarked class ConfigMigrationV1 implements ConfigMigration { @Override @@ -17,7 +18,7 @@ public int sourceVersion() { } @Override - public @NotNull ConfigurationSection migrate(@NotNull ConfigurationSection root) { + public ConfigurationSection migrate(ConfigurationSection root) { // check if config is still using old path String obfuscationConfigPath = root.contains("world") ? "world" : "obfuscation"; convertSectionListToSection(root, obfuscationConfigPath); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV2.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV2.java index 4ceb1b3a..89e1ccb9 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV2.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV2.java @@ -1,9 +1,11 @@ package dev.imprex.orebfuscator.config.migrations; -import org.jetbrains.annotations.NotNull; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; import dev.imprex.orebfuscator.util.BlockPos; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked class ConfigMigrationV2 implements ConfigMigration { @Override @@ -12,13 +14,13 @@ public int sourceVersion() { } @Override - public @NotNull ConfigurationSection migrate(@NotNull ConfigurationSection root) { + public ConfigurationSection migrate(ConfigurationSection root) { convertRandomBlocksToSections(root.getSection("obfuscation")); convertRandomBlocksToSections(root.getSection("proximity")); return root; } - private static void convertRandomBlocksToSections(ConfigurationSection configContainer) { + private static void convertRandomBlocksToSections(@Nullable ConfigurationSection configContainer) { if (configContainer == null) { return; } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV3.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV3.java index 460dab21..ec5c8b93 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV3.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV3.java @@ -2,10 +2,11 @@ import java.util.List; import java.util.Map; -import org.jetbrains.annotations.NotNull; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; import dev.imprex.orebfuscator.util.BlockPos; +import org.jspecify.annotations.NullMarked; +@NullMarked class ConfigMigrationV3 implements ConfigMigration { @Override @@ -14,7 +15,7 @@ public int sourceVersion() { } @Override - public @NotNull ConfigurationSection migrate(@NotNull ConfigurationSection root) { + public ConfigurationSection migrate(ConfigurationSection root) { migrateAdvancedConfig(root); migrateCacheConfig(root); migrateProximityConfigs(root); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV4.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV4.java index c72a40df..1f9882cd 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV4.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV4.java @@ -1,8 +1,10 @@ package dev.imprex.orebfuscator.config.migrations; -import org.jetbrains.annotations.NotNull; import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked class ConfigMigrationV4 implements ConfigMigration { @Override @@ -11,13 +13,13 @@ public int sourceVersion() { } @Override - public @NotNull ConfigurationSection migrate(@NotNull ConfigurationSection root) { + public ConfigurationSection migrate(ConfigurationSection root) { migrateWorlds(root.getSection("obfuscation")); migrateWorlds(root.getSection("proximity")); return root; } - private static void migrateWorlds(ConfigurationSection configContainer) { + private static void migrateWorlds(@Nullable ConfigurationSection configContainer) { if (configContainer == null) { return; } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV5.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV5.java new file mode 100644 index 00000000..dda68643 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrationV5.java @@ -0,0 +1,23 @@ +package dev.imprex.orebfuscator.config.migrations; + +import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; +import java.util.List; +import java.util.Map; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class ConfigMigrationV5 implements ConfigMigration { + + @Override + public int sourceVersion() { + return 5; + } + + @Override + public ConfigurationSection migrate(ConfigurationSection root) { + ConfigMigration.migrateNames(root.getSection("advanced"), List.of( + Map.entry("obfuscation.threads", "threads") + )); + return root; + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrator.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrator.java index a43ca11e..79bf0fe4 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrator.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/migrations/ConfigMigrator.java @@ -5,7 +5,9 @@ import dev.imprex.orebfuscator.config.yaml.ConfigurationSection; import dev.imprex.orebfuscator.logging.OfcLogger; +import org.jspecify.annotations.NullMarked; +@NullMarked public class ConfigMigrator { private static final Map MIGRATIONS = new HashMap<>(); @@ -15,6 +17,7 @@ public class ConfigMigrator { register(new ConfigMigrationV2()); register(new ConfigMigrationV3()); register(new ConfigMigrationV4()); + register(new ConfigMigrationV5()); } private static void register(ConfigMigration migration) { diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/ConfigurationSection.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/ConfigurationSection.java index 966c6a5b..dac9fc34 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/ConfigurationSection.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/ConfigurationSection.java @@ -15,9 +15,10 @@ import java.util.Set; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class ConfigurationSection { static final char PATH_SEPARATOR = '.'; @@ -25,27 +26,25 @@ public class ConfigurationSection { protected final Map map = new LinkedHashMap<>(); private final String path; - protected ConfigurationSection(@NotNull String path) { + protected ConfigurationSection(String path) { Objects.requireNonNull(path, "Path cannot be null"); this.path = path; } - @NotNull public String getName() { return path; } - @NotNull public Set getKeys() { return this.map.keySet(); } - public boolean contains(@NotNull String path) { + public boolean contains(String path) { return get(path, null) != null; } - public void set(@NotNull String path, @Nullable Object value) { + public void set(String path, @Nullable Object value) { Objects.requireNonNull(path, "Path cannot be null"); int end = -1, start; @@ -76,14 +75,13 @@ public void set(@NotNull String path, @Nullable Object value) { } } - @Nullable - public Object get(@NotNull String path) { + public @Nullable Object get(String path) { return get(path, null); } - @Nullable + @Contract("_, !null -> !null") - public Object get(@NotNull String path, @Nullable Object defaultValue) { + public @Nullable Object get(String path, @Nullable Object defaultValue) { Objects.requireNonNull(path, "Path cannot be null"); if (path.isBlank()) { @@ -107,8 +105,7 @@ public Object get(@NotNull String path, @Nullable Object defaultValue) { return section.get(segment, defaultValue); } - @NotNull - public ConfigurationSection createSection(@NotNull String path) { + public ConfigurationSection createSection(String path) { Objects.requireNonNull(path, "Cannot create section at empty path"); if (path.isBlank()) { @@ -136,108 +133,95 @@ public ConfigurationSection createSection(@NotNull String path) { return section.createSection(segment); } - public boolean isBoolean(@NotNull String path) { + public boolean isBoolean(String path) { return get(path) instanceof Boolean; } - @Nullable - public Boolean getBoolean(@NotNull String path) { + public @Nullable Boolean getBoolean(String path) { return getBoolean(path, null); } - @Nullable @Contract("_, !null -> !null") - public Boolean getBoolean(@NotNull String path, @Nullable Boolean defaultValue) { + public @Nullable Boolean getBoolean(String path, @Nullable Boolean defaultValue) { return get(path, defaultValue) instanceof Boolean value ? value : defaultValue; } - public boolean isInt(@NotNull String path) { + public boolean isInt(String path) { return get(path) instanceof Integer; } - @Nullable - public Integer getInt(@NotNull String path) { + public @Nullable Integer getInt(String path) { return getInt(path, null); } - @Nullable @Contract("_, !null -> !null") - public Integer getInt(@NotNull String path, @Nullable Integer defaultValue) { + public @Nullable Integer getInt(String path, @Nullable Integer defaultValue) { return get(path, defaultValue) instanceof Number value ? Integer.valueOf(value.intValue()) : defaultValue; } - public boolean isLong(@NotNull String path) { + public boolean isLong(String path) { return get(path) instanceof Long; } - @Nullable - public Long getLong(@NotNull String path) { + public @Nullable Long getLong(String path) { return getLong(path, null); } - @Nullable @Contract("_, !null -> !null") - public Long getLong(@NotNull String path, @Nullable Long defaultValue) { + public @Nullable Long getLong(String path, @Nullable Long defaultValue) { return get(path, defaultValue) instanceof Number value ? Long.valueOf(value.longValue()) : defaultValue; } - public boolean isDouble(@NotNull String path) { + public boolean isDouble(String path) { return get(path) instanceof Double; } - @Nullable - public Double getDouble(@NotNull String path) { + public @Nullable Double getDouble(String path) { return getDouble(path, null); } - @Nullable @Contract("_, !null -> !null") - public Double getDouble(@NotNull String path, @Nullable Double defaultValue) { + public @Nullable Double getDouble(String path, @Nullable Double defaultValue) { return get(path, defaultValue) instanceof Number value ? Double.valueOf(value.doubleValue()) : defaultValue; } - public boolean isNumber(@NotNull String path) { + public boolean isNumber(String path) { return get(path) instanceof Number; } - public boolean isString(@NotNull String path) { + public boolean isString(String path) { return get(path) instanceof String; } - @Nullable - public String getString(@NotNull String path) { + public @Nullable String getString(String path) { return getString(path, null); } - @Nullable @Contract("_, !null -> !null") - public String getString(@NotNull String path, @Nullable String defaultValue) { + public @Nullable String getString(String path, @Nullable String defaultValue) { return get(path, defaultValue) instanceof String value ? value : defaultValue; } - public boolean isList(@NotNull String path) { + public boolean isList(String path) { return get(path) instanceof List; } - @Nullable - public List getList(@NotNull String path) { + public @Nullable List getList(String path) { return getList(path, null); } - @Nullable @Contract("_, !null -> !null") - public List getList(@NotNull String path, List defaultValue) { + public @Nullable List getList(String path, @Nullable List defaultValue) { return get(path, defaultValue) instanceof List value ? value : defaultValue; } - @NotNull - public List getStringList(@NotNull String path) { + public List getStringList(String path) { List list = getList(path); if (list == null) { return Collections.emptyList(); @@ -254,17 +238,15 @@ public List getStringList(@NotNull String path) { return result; } - public boolean isSection(@NotNull String path) { + public boolean isSection(String path) { return get(path) instanceof ConfigurationSection; } - @Nullable - public ConfigurationSection getSection(@NotNull String path) { + public @Nullable ConfigurationSection getSection(String path) { return get(path) instanceof ConfigurationSection value ? value : null; } - @NotNull public List getSubSections() { List result = new ArrayList<>(); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/InvalidConfigurationException.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/InvalidConfigurationException.java index 7cb14620..2c1f827f 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/InvalidConfigurationException.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/InvalidConfigurationException.java @@ -6,7 +6,9 @@ */ package dev.imprex.orebfuscator.config.yaml; -@SuppressWarnings("serial") +import org.jspecify.annotations.NullMarked; + +@NullMarked public class InvalidConfigurationException extends Exception { public InvalidConfigurationException(String message) { diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/NodeCommentData.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/NodeCommentData.java index 94822213..af3dac94 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/NodeCommentData.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/NodeCommentData.java @@ -4,9 +4,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.yaml.snakeyaml.comments.CommentLine; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; @@ -14,14 +13,15 @@ import org.yaml.snakeyaml.nodes.ScalarNode; import org.yaml.snakeyaml.nodes.SequenceNode; +@NullMarked class NodeCommentData { private final List blockComments; private final List inLineComments; - private final List endComments; + private final @Nullable List endComments; private final Map children = new HashMap<>(); - private final Map> sequenceValueComments; + private final @Nullable Map> sequenceValueComments; public NodeCommentData() { this.blockComments = Collections.emptyList(); @@ -30,7 +30,7 @@ public NodeCommentData() { this.sequenceValueComments = null; } - public NodeCommentData(@NotNull NodeTuple nodeTuple) { + public NodeCommentData(NodeTuple nodeTuple) { Node keyNode = nodeTuple.getKeyNode(); Node valueNode = nodeTuple.getValueNode(); @@ -60,23 +60,23 @@ public NodeCommentData(@NotNull NodeTuple nodeTuple) { } } - public NodeCommentData(@NotNull MappingNode node) { + public NodeCommentData(MappingNode node) { this.blockComments = node.getBlockComments(); this.inLineComments = node.getInLineComments(); this.endComments = node.getEndComments(); this.sequenceValueComments = null; } - public void addChild(@NotNull String key, @NotNull NodeCommentData commentData) { + public void addChild(String key, NodeCommentData commentData) { this.children.put(key, commentData); } @Nullable - public NodeCommentData getChild(@NotNull String key) { + public NodeCommentData getChild(String key) { return this.children.get(key); } - public void apply(@NotNull NodeTuple nodeTuple) { + public void apply(NodeTuple nodeTuple) { Node keyNode = nodeTuple.getKeyNode(); Node valueNode = nodeTuple.getValueNode(); @@ -103,7 +103,7 @@ public void apply(@NotNull NodeTuple nodeTuple) { } } - public void apply(@NotNull MappingNode node) { + public void apply(MappingNode node) { node.setBlockComments(this.blockComments); node.setInLineComments(this.inLineComments); node.setEndComments(this.endComments); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/YamlConfiguration.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/YamlConfiguration.java index b517c396..89cfd990 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/YamlConfiguration.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/YamlConfiguration.java @@ -18,8 +18,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; @@ -32,10 +32,10 @@ import org.yaml.snakeyaml.reader.UnicodeReader; import org.yaml.snakeyaml.representer.Representer; +@NullMarked public class YamlConfiguration extends ConfigurationSection { - @NotNull - public static YamlConfiguration loadConfig(@NotNull Path path) throws IOException, InvalidConfigurationException { + public static YamlConfiguration loadConfig(Path path) throws IOException, InvalidConfigurationException { Objects.requireNonNull(path, "Path cannot be null"); try (InputStream inputStream = Files.newInputStream(path)) { @@ -43,8 +43,7 @@ public static YamlConfiguration loadConfig(@NotNull Path path) throws IOExceptio } } - @NotNull - public static YamlConfiguration loadConfig(@NotNull InputStream inputStream) + public static YamlConfiguration loadConfig(InputStream inputStream) throws IOException, InvalidConfigurationException { Objects.requireNonNull(inputStream, "InputStream cannot be null"); @@ -82,7 +81,7 @@ public void clear() { this.map.clear(); } - public void save(@NotNull Path path) throws IOException { + public void save(Path path) throws IOException { Objects.requireNonNull(path, "Path cannot be null"); Files.createDirectories(path.getParent()); @@ -101,7 +100,7 @@ public String withoutComments() throws IOException { } } - private void save(@NotNull Writer writer) throws IOException { + private void save(Writer writer) throws IOException { Objects.requireNonNull(writer, "Writer cannot be null"); MappingNode node = toNodeTree(this.commentData, this); @@ -117,7 +116,7 @@ private void save(@NotNull Writer writer) throws IOException { } } - private void load(@NotNull Reader reader) throws YAMLException, InvalidConfigurationException { + private void load(Reader reader) throws YAMLException, InvalidConfigurationException { Objects.requireNonNull(reader, "Reader cannot be null"); Node rawNode = yaml.compose(reader); @@ -134,7 +133,7 @@ private void load(@NotNull Reader reader) throws YAMLException, InvalidConfigura } } - private MappingNode toNodeTree(@Nullable NodeCommentData commentData, @NotNull ConfigurationSection section) { + private MappingNode toNodeTree(@Nullable NodeCommentData commentData, ConfigurationSection section) { List nodeTuples = new ArrayList<>(); for (Map.Entry entry : section.map.entrySet()) { @@ -163,8 +162,8 @@ private MappingNode toNodeTree(@Nullable NodeCommentData commentData, @NotNull C return new MappingNode(Tag.MAP, nodeTuples, DumperOptions.FlowStyle.BLOCK); } - private void fromNodeTree(@NotNull MappingNode input, @NotNull NodeCommentData commentData, - @NotNull ConfigurationSection section) throws InvalidConfigurationException { + private void fromNodeTree(MappingNode input, NodeCommentData commentData, ConfigurationSection section) + throws InvalidConfigurationException { constructor.flattenMapping(input); for (NodeTuple nodeTuple : input.getValue()) { @@ -191,7 +190,7 @@ private void fromNodeTree(@NotNull MappingNode input, @NotNull NodeCommentData c } } - private boolean isNullOrEmpty(Collection collection) { + private boolean isNullOrEmpty(@Nullable Collection collection) { return collection == null || collection.isEmpty(); } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkAccessor.java new file mode 100644 index 00000000..144a744e --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkAccessor.java @@ -0,0 +1,27 @@ +package dev.imprex.orebfuscator.interop; + +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public interface ChunkAccessor { + + ChunkAccessor EMPTY = (x, y, z) -> 0; + + static long chunkCoordsToLong(int chunkX, int chunkZ) { + return (chunkZ & 0xffffffffL) << 32 | chunkX & 0xffffffffL; + } + + @Contract("null -> true; !null -> _") + static boolean isNullOrEmpty(@Nullable ChunkAccessor chunkAccessor) { + return chunkAccessor == null || chunkAccessor == EMPTY; + } + + @Contract("null -> !null; !null -> param1") + static ChunkAccessor ofNullable(@Nullable ChunkAccessor chunkAccessor) { + return chunkAccessor == null ? EMPTY : chunkAccessor; + } + + int getBlockState(int x, int y, int z); +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkPacketAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkPacketAccessor.java index e77b8533..09e42b4b 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkPacketAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkPacketAccessor.java @@ -1,12 +1,11 @@ package dev.imprex.orebfuscator.interop; -import java.util.function.Predicate; -import dev.imprex.orebfuscator.util.BlockPos; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.obfuscation.ObfuscationResponse; +@NullMarked public interface ChunkPacketAccessor { - WorldAccessor world(); - int chunkX(); int chunkZ(); @@ -15,7 +14,6 @@ public interface ChunkPacketAccessor { byte[] data(); - void setData(byte[] data); + void update(ObfuscationResponse response); - void filterBlockEntities(Predicate predicate); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/OrebfuscatorCore.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/OrebfuscatorCore.java new file mode 100644 index 00000000..79cb2887 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/OrebfuscatorCore.java @@ -0,0 +1,37 @@ +package dev.imprex.orebfuscator.interop; + +import dev.imprex.orebfuscator.statistics.StatisticsRegistry; +import dev.imprex.orebfuscator.util.Version; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.cache.ObfuscationCache; +import dev.imprex.orebfuscator.chunk.ChunkFactory; +import dev.imprex.orebfuscator.config.OrebfuscatorConfig; +import dev.imprex.orebfuscator.obfuscation.ObfuscationPipeline; +import dev.imprex.orebfuscator.obfuscation.ObfuscationProcessor; +import dev.imprex.orebfuscator.statistics.OrebfuscatorStatistics; +import dev.imprex.orebfuscator.util.concurrent.OrebfuscatorExecutor; + +@NullMarked +public interface OrebfuscatorCore extends ServerAccessor { + + ThreadGroup THREAD_GROUP = new ThreadGroup("orebfuscator"); + + String name(); + + OrebfuscatorExecutor executor(); + + StatisticsRegistry statisticsRegistry(); + + OrebfuscatorStatistics statistics(); + + OrebfuscatorConfig config(); + + ChunkFactory chunkFactory(); + + ObfuscationCache cache(); + + ObfuscationPipeline obfuscationPipeline(); + + ObfuscationProcessor obfuscationProcessor(); + +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/PlayerAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/PlayerAccessor.java new file mode 100644 index 00000000..dd8c0f77 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/PlayerAccessor.java @@ -0,0 +1,31 @@ +package dev.imprex.orebfuscator.interop; + +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.PermissionRequirements; +import dev.imprex.orebfuscator.player.OrebfuscatorPlayer; +import dev.imprex.orebfuscator.util.BlockPos; +import dev.imprex.orebfuscator.util.EntityPose; + +@NullMarked +public interface PlayerAccessor { + + OrebfuscatorPlayer orebfuscatorPlayer(); + + EntityPose pose(); + + EntityPose eyePose(); + + WorldAccessor world(); + + boolean isAlive(); + + boolean isSpectator(); + + double lavaFogDistance(); + + boolean hasPermission(PermissionRequirements permission); + + void runForPlayer(Runnable runnable); + + void sendBlockUpdates(Iterable iterable); +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/RegistryAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/RegistryAccessor.java index 3f316c2b..b38e183e 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/RegistryAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/RegistryAccessor.java @@ -1,10 +1,12 @@ package dev.imprex.orebfuscator.interop; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockTag; +@NullMarked public interface RegistryAccessor { int getUniqueBlockStateCount(); @@ -13,12 +15,14 @@ public interface RegistryAccessor { boolean isAir(int blockId); + boolean isLava(int blockId); + boolean isOccluding(int blockId); boolean isBlockEntity(int blockId); - @Nullable BlockProperties getBlockByName(@NotNull String name); + @Nullable BlockProperties getBlockByName(String name); - @Nullable BlockTag getBlockTagByName(@NotNull String name); + @Nullable BlockTag getBlockTagByName(String name); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ServerAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ServerAccessor.java index c10e72b4..71091895 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ServerAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ServerAccessor.java @@ -2,20 +2,28 @@ import java.nio.file.Path; import java.util.List; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.util.Version; +@NullMarked public interface ServerAccessor { - Path getConfigDirectory(); + boolean isGameThread(); - Path getWorldDirectory(); + Path configDirectory(); - String getOrebfuscatorVersion(); + Path worldDirectory(); - Version getMinecraftVersion(); + Version orebfuscatorVersion(); - RegistryAccessor getRegistry(); + Version minecraftVersion(); - List getWorlds(); + RegistryAccessor registry(); + AbstractRegionFileCache createRegionFileCache(); + + List worlds(); + + List players(); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/WorldAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/WorldAccessor.java index 9661af57..56c9119b 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/WorldAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/WorldAccessor.java @@ -1,20 +1,38 @@ package dev.imprex.orebfuscator.interop; +import dev.imprex.orebfuscator.util.ChunkDirection; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.annotations.CheckReturnValue; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.config.api.WorldConfigBundle; +import dev.imprex.orebfuscator.obfuscation.ObfuscationRequest; +import dev.imprex.orebfuscator.util.BlockPos; +import org.jspecify.annotations.Nullable; + +@NullMarked public interface WorldAccessor { - String getName(); + String name(); - int getHeight(); + int height(); - int getMinBuildHeight(); + int minBuildHeight(); - int getMaxBuildHeight(); + int maxBuildHeight(); - int getSectionCount(); + int sectionCount(); - int getMinSection(); + int minSection(); - int getMaxSection(); + int maxSection(); - int getSectionIndex(int y); + int sectionIndex(int y); + + WorldConfigBundle config(); + + CompletableFuture getNeighboringChunks(ObfuscationRequest request); + + ChunkAccessor getChunkNow(int chunkX, int chunkZ); + + void sendBlockUpdates(Iterable iterable); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LoggerAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LoggerAccessor.java index 22bade65..c1159095 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LoggerAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LoggerAccessor.java @@ -1,10 +1,11 @@ package dev.imprex.orebfuscator.logging; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public interface LoggerAccessor { - void log(@NotNull LogLevel level, @NotNull String message, @Nullable Throwable throwable); + void log(LogLevel level, String message, @Nullable Throwable throwable); } \ No newline at end of file diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/OfcLogger.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/OfcLogger.java index 00b16f33..a0b5ad32 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/OfcLogger.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/OfcLogger.java @@ -3,10 +3,10 @@ import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - +@NullMarked public class OfcLogger { private static LoggerAccessor logger = new SystemLogger(); @@ -14,7 +14,7 @@ public class OfcLogger { private static final Queue VERBOSE_LOG = new ConcurrentLinkedQueue<>(); private static boolean verbose; - public static void setLogger(@NotNull LoggerAccessor logger) { + public static void setLogger(LoggerAccessor logger) { if (OfcLogger.logger instanceof SystemLogger) { OfcLogger.logger = Objects.requireNonNull(logger); } @@ -29,36 +29,35 @@ public static void setVerboseLogging(boolean enabled) { } } - @NotNull public static String getLatestVerboseLog() { return String.join("\n", VERBOSE_LOG); } - public static void debug(@NotNull String message) { + public static void debug(String message) { log(LogLevel.DEBUG, message); } - public static void info(@NotNull String message) { + public static void info(String message) { log(LogLevel.INFO, message); } - public static void warn(@NotNull String message) { + public static void warn(String message) { log(LogLevel.WARN, message); } - public static void error(@NotNull Throwable throwable) { + public static void error(Throwable throwable) { log(LogLevel.ERROR, "An error occurred:", throwable); } - public static void error(@NotNull String message, @Nullable Throwable throwable) { + public static void error(String message, @Nullable Throwable throwable) { log(LogLevel.ERROR, message, throwable); } - public static void log(@NotNull LogLevel level, @NotNull String message) { + public static void log(LogLevel level, String message) { log(level, message, null); } - public static void log(@NotNull LogLevel level, @NotNull String message, @Nullable Throwable throwable) { + public static void log(LogLevel level, String message, @Nullable Throwable throwable) { Objects.requireNonNull(level); Objects.requireNonNull(message); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/SystemLogger.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/SystemLogger.java index 921a2f07..51c17f55 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/SystemLogger.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/SystemLogger.java @@ -1,14 +1,14 @@ package dev.imprex.orebfuscator.logging; import java.io.PrintStream; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - +@NullMarked public class SystemLogger implements LoggerAccessor { @Override - public void log(@NotNull LogLevel level, @NotNull String message, @Nullable Throwable throwable) { + public void log(LogLevel level, String message, @Nullable Throwable throwable) { PrintStream stream = level == LogLevel.ERROR ? System.err : System.out; stream.printf("[Orebfuscator - %s] %s%n", level, message); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/DeobfuscationWorker.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/DeobfuscationWorker.java new file mode 100644 index 00000000..81fd8dc2 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/DeobfuscationWorker.java @@ -0,0 +1,126 @@ +package dev.imprex.orebfuscator.obfuscation; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import org.jspecify.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.cache.ObfuscationCache; +import dev.imprex.orebfuscator.config.OrebfuscatorConfig; +import dev.imprex.orebfuscator.config.api.BlockFlags; +import dev.imprex.orebfuscator.config.api.ObfuscationConfig; +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.statistics.ObfuscationStatistics; +import dev.imprex.orebfuscator.util.BlockPos; +import dev.imprex.orebfuscator.util.ChunkCacheKey; + +@NullMarked +public class DeobfuscationWorker { + + private static List precomputeOffsets(int radius) { + List> offset = new ArrayList<>(); + + for (int dx = -radius; dx <= radius; dx++) { + for (int dy = -radius; dy <= radius; dy++) { + for (int dz = -radius; dz <= radius; dz++) { + int distance = Math.abs(dx) + Math.abs(dy) + Math.abs(dz); + if (distance <= radius) { + offset.add(Map.entry(new BlockPos(dx, dy, dz), distance)); + } + } + } + } + + offset + .sort(Comparator.comparingInt((Entry e) -> e.getValue()).thenComparing(Entry::getKey)); + + return offset.stream().map(Entry::getKey).toList(); + } + + private final OrebfuscatorConfig config; + private final ObfuscationCache cache; + private final ObfuscationStatistics statistics; + + private final List offsets; + + public DeobfuscationWorker(OrebfuscatorCore orebfuscator) { + this.config = orebfuscator.config(); + this.cache = orebfuscator.cache(); + this.statistics = orebfuscator.statistics().obfuscation; + + this.offsets = precomputeOffsets(orebfuscator.config().general().updateRadius()); + } + + public void deobfuscate(WorldAccessor world, @Nullable BlockPos block) { + Objects.requireNonNull(world); + if (block == null) { + return; + } + + deobfuscate(world, List.of(block)); + } + + public void deobfuscate(WorldAccessor world, @Nullable List blocks) { + Objects.requireNonNull(world); + if (blocks == null || blocks.isEmpty()) { + return; + } + + ObfuscationConfig obfuscationConfig = world.config().obfuscation(); + if (obfuscationConfig == null || !obfuscationConfig.isEnabled()) { + return; + } + + var timer = statistics.debofuscation.start(); + try { + this.deobfuscateNow(world, blocks); + } finally { + timer.stop(); + } + } + + private void deobfuscateNow(WorldAccessor world, List blocks) { + final BlockFlags blockFlags = world.config().blockFlags(); + + final Map chunks = new HashMap<>(); + final Set updatedBlocks = new HashSet<>(); + final Set invalidChunks = new HashSet<>(); + + for (BlockPos block : blocks) { + for (var offset : offsets) { + BlockPos position = block.add(offset); + + int chunkX = position.x() >> 4; + int chunkZ = position.z() >> 4; + + long key = ChunkAccessor.chunkCoordsToLong(chunkX, chunkZ); + ChunkAccessor chunk = chunks.computeIfAbsent(key, k -> world.getChunkNow(chunkX, chunkZ)); + if (ChunkAccessor.isNullOrEmpty(chunk)) { + continue; + } + + int blockState = chunk.getBlockState(position.x(), position.y(), position.z()); + if (!(BlockFlags.isObfuscateBitSet(blockFlags.flags(blockState)) && updatedBlocks.add(position))) { + continue; + } + + if (config.cache().enabled()) { + ChunkCacheKey chunkPosition = new ChunkCacheKey(world, position); + if (invalidChunks.add(chunkPosition)) { + cache.invalidate(chunkPosition); + } + } + } + } + + world.sendBlockUpdates(updatedBlocks); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationPipeline.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationPipeline.java new file mode 100644 index 00000000..2d162b63 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationPipeline.java @@ -0,0 +1,152 @@ +package dev.imprex.orebfuscator.obfuscation; + +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import dev.imprex.orebfuscator.cache.CacheRequest; +import dev.imprex.orebfuscator.cache.ObfuscationCache; +import dev.imprex.orebfuscator.config.api.AdvancedConfig; +import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import dev.imprex.orebfuscator.interop.ChunkPacketAccessor; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.interop.PlayerAccessor; +import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.statistics.OrebfuscatorStatistics; +import dev.imprex.orebfuscator.util.ChunkCacheKey; +import dev.imprex.orebfuscator.util.concurrent.OrebfuscatorExecutor; + +@NullMarked +public class ObfuscationPipeline { + + private final Config config; + private final ObfuscationCache cache; + private final ObfuscationProcessor processor; + private final OrebfuscatorStatistics statistics; + private final OrebfuscatorExecutor executor; + + public ObfuscationPipeline(OrebfuscatorCore orebfuscator) { + this.config = orebfuscator.config(); + this.cache = orebfuscator.cache(); + this.processor = orebfuscator.obfuscationProcessor(); + this.statistics = orebfuscator.statistics(); + this.executor = orebfuscator.executor(); + } + + public CompletionStage request( + WorldAccessor world, + PlayerAccessor player, + ChunkPacketAccessor packet, + @Nullable ChunkAccessor @Nullable [] neighborChunks) { + var timer = statistics.injector.pipelineDelayTotal.start(); + return timer.wrap(requestInternal(world, player, packet, neighborChunks)); + } + + private CompletionStage requestInternal( + WorldAccessor world, + PlayerAccessor player, + ChunkPacketAccessor packet, + @Nullable ChunkAccessor @Nullable [] neighborChunks) { + + final var request = new ObfuscationRequest(world, player, packet, neighborChunks); + + final CacheRequest cacheRequest; + final CompletionStage> cacheFuture; + + if (config.cache().enabled()) { + ChunkCacheKey cacheKey = new ChunkCacheKey(request); + + byte[] hash = CacheRequest.HASH_FUNCTION.newHasher() + .putBytes(config.systemHash()) + .putBytes(request.packet().data()) + .hash() + .asBytes(); + + cacheRequest = new CacheRequest(cacheKey, hash); + + var cacheTimer = statistics.injector.pipelineDelayCache.start(); + cacheFuture = cacheTimer.wrap(this.cache.get(cacheRequest)); + } else { + cacheRequest = null; + cacheFuture = CompletableFuture.completedStage(Optional.empty()); + } + + var future = cacheFuture.thenComposeAsync(optional -> { + if (optional.isPresent()) { + return CompletableFuture.completedStage(optional.get()); + } else { + final var neighborTimer = statistics.injector.pipelineDelayNeighbors.start(); + return neighborTimer.wrap(world.getNeighboringChunks(request)) + .handleAsync((neighbors, throwable) -> { + if (throwable != null) { + OfcLogger.error("Can't get neighboring chunks for (%d, %d)".formatted(packet.chunkX(), packet.chunkZ()), + throwable); + return request; + } + + long missingChunks = Arrays.stream(neighbors).filter(ChunkAccessor::isNullOrEmpty).count(); + statistics.obfuscation.missingNeighboringChunks.add(missingChunks); + + return request.withNeighbors(neighbors); + }, this.executor) + .thenApply(this.processor::process) + .thenApply(response -> { + if (config.cache().enabled() && cacheRequest != null) { + cache.add(cacheRequest, response); + } + return response; + }); + } + }, this.executor); + + AdvancedConfig advancedConfig = config.advanced(); + if (advancedConfig.hasObfuscationTimeout()) { + future = future + .toCompletableFuture() + .orTimeout(advancedConfig.obfuscationTimeout(), TimeUnit.MILLISECONDS); + } + + return future.thenApplyAsync(response -> { + this.postProcess(request, response); + return null; + }, this.executor).exceptionallyAsync(throwable -> { + this.handleExceptions(request, throwable); + return null; + }, this.executor); + } + + private void postProcess(ObfuscationRequest request, ObfuscationResponse response) { + var packet = request.packet(); + + statistics.obfuscation.originalChunkSize.add(packet.data().length); + statistics.obfuscation.obfuscatedChunkSize.add(response.data().length); + + packet.update(response); + + var player = request.player().orebfuscatorPlayer(); + player.addChunk(request.world(), packet.chunkX(), packet.chunkZ(), response.proximityBlocks()); + } + + private void handleExceptions(ObfuscationRequest request, Throwable throwable) { + var packet = request.packet(); + + if (throwable instanceof CompletionException && throwable.getCause() != null) { + throwable = throwable.getCause(); + } + + if (throwable instanceof TimeoutException) { + OfcLogger.warn("Obfuscation for chunk[world=%s, x=%d, z=%d] timed out".formatted(request.world().name(), + packet.chunkX(), packet.chunkZ())); + } else { + OfcLogger.error("An error occurred while obfuscating chunk[world=%s, x=%d, z=%d]" + .formatted(request.world().name(), packet.chunkX(), packet.chunkZ()), throwable); + } + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java new file mode 100644 index 00000000..c39c01fe --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java @@ -0,0 +1,215 @@ +package dev.imprex.orebfuscator.obfuscation; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.random.RandomGenerator; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.chunk.Chunk; +import dev.imprex.orebfuscator.chunk.ChunkFactory; +import dev.imprex.orebfuscator.chunk.ChunkSection; +import dev.imprex.orebfuscator.config.ProximityHeightCondition; +import dev.imprex.orebfuscator.config.api.BlockFlags; +import dev.imprex.orebfuscator.config.api.ObfuscationConfig; +import dev.imprex.orebfuscator.config.api.ProximityConfig; +import dev.imprex.orebfuscator.config.api.WorldConfigBundle; +import dev.imprex.orebfuscator.interop.ChunkPacketAccessor; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.interop.RegistryAccessor; +import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.player.ProximityBlock; +import dev.imprex.orebfuscator.statistics.OrebfuscatorStatistics; +import dev.imprex.orebfuscator.util.BlockPos; +import dev.imprex.orebfuscator.util.concurrent.OrebfuscatorThread; + +@NullMarked +public class ObfuscationProcessor { + + private static final ThreadLocal STATE = ThreadLocal.withInitial(State::new); + + private final ChunkFactory chunkFactory; + private final RegistryAccessor registryAccessor; + private final OrebfuscatorStatistics statistics; + + public ObfuscationProcessor(OrebfuscatorCore orebfuscator) { + this.chunkFactory = orebfuscator.chunkFactory(); + this.registryAccessor = orebfuscator.registry(); + this.statistics = orebfuscator.statistics(); + } + + public ObfuscationResponse process(ObfuscationRequest request) { + var timer = statistics.injector.pipelineDelayProcessor.start(); + try { + return processInternal(request); + } finally { + timer.stop(); + } + } + + private ObfuscationResponse processInternal(ObfuscationRequest request) { + ChunkPacketAccessor packet = request.packet(); + WorldAccessor worldAccessor = request.world(); + + WorldConfigBundle bundle = worldAccessor.config(); + BlockFlags blockFlags = bundle.blockFlags(); + ObfuscationConfig obfuscationConfig = bundle.obfuscation(); + ProximityConfig proximityConfig = bundle.proximity(); + + var state = STATE.get(); + + Set blockEntities = new HashSet<>(); + List proximityBlocks = new ArrayList<>(); + + RandomGenerator random = ThreadLocalRandom.current(); + if (Thread.currentThread() instanceof OrebfuscatorThread obfuscationThread) { + random = obfuscationThread.random(); + } + + int baseX = packet.chunkX() << 4; + int baseZ = packet.chunkZ() << 4; + + int layerY = Integer.MIN_VALUE; + int layerYBlockState = -1; + + try (Chunk chunk = this.chunkFactory.fromPacket(request)) { + for (int sectionIndex = Math.max(0, bundle.minSectionIndex()); sectionIndex <= Math + .min(chunk.getSectionCount() - 1, bundle.maxSectionIndex()); sectionIndex++) { + ChunkSection chunkSection = chunk.getSection(sectionIndex); + if (chunkSection == null || chunkSection.isEmpty()) { + continue; + } + + final int baseY = worldAccessor.minBuildHeight() + (sectionIndex << 4); + for (int index = 0; index < 4096; index++) { + int y = baseY + (index >> 8 & 15); + if (!bundle.shouldObfuscate(y)) { + continue; + } + + int blockState = chunkSection.getBlockState(index); + + int obfuscateBits = blockFlags.flags(blockState, y); + if (BlockFlags.isEmpty(obfuscateBits)) { + continue; + } + + int x = baseX + (index & 15); + int z = baseZ + (index >> 4 & 15); + + boolean isObfuscateBitSet = BlockFlags.isObfuscateBitSet(obfuscateBits); + boolean obfuscated = false; + + // should current block be obfuscated + if (isObfuscateBitSet && obfuscationConfig != null && obfuscationConfig.shouldObfuscate(y) + && shouldObfuscate(request, chunk, state, x, y, z)) { + if (state.isLava) { + proximityBlocks.add(new ProximityBlock(new BlockPos(x, y, z), true)); + } + if (obfuscationConfig.layerObfuscation()) { + if (layerY != y) { + layerY = y; + layerYBlockState = bundle.nextRandomObfuscationBlock(random, y); + } + blockState = layerYBlockState; + } else { + blockState = bundle.nextRandomObfuscationBlock(random, y); + } + obfuscated = true; + } + + // should current block be proximity hidden + if (!obfuscated && BlockFlags.isProximityBitSet(obfuscateBits) && proximityConfig != null + && proximityConfig.shouldObfuscate(y)) { + proximityBlocks.add(new ProximityBlock(new BlockPos(x, y, z), false)); + if (BlockFlags.isUseBlockBelowBitSet(obfuscateBits)) { + boolean allowNonOcclude = !isObfuscateBitSet || !ProximityHeightCondition.isPresent(obfuscateBits); + blockState = getBlockStateBelow(random, bundle, chunk, x, y, z, allowNonOcclude); + } else { + blockState = bundle.nextRandomProximityBlock(random, y); + } + obfuscated = true; + } + + // update block state if needed + if (obfuscated) { + chunkSection.setBlockState(index, blockState); + if (BlockFlags.isBlockEntityBitSet(obfuscateBits)) { + blockEntities.add(new BlockPos(x, y, z)); + } + } + + state.reset(); + } + } + + return new ObfuscationResponse(chunk.finalizeOutput(), blockEntities, proximityBlocks); + } + } + + // returns first block below given position that wouldn't be obfuscated in any + // way at given position + private int getBlockStateBelow(RandomGenerator random, WorldConfigBundle bundle, Chunk chunk, int x, int y, int z, + boolean allowNonOcclude) { + BlockFlags blockFlags = bundle.blockFlags(); + + for (int targetY = y - 1; targetY > chunk.world().minBuildHeight(); targetY--) { + int blockData = chunk.getBlockState(x, targetY, z); + if (blockData != -1 && (allowNonOcclude || registryAccessor.isOccluding(blockData))) { + int mask = blockFlags.flags(blockData, y); + if (BlockFlags.isEmpty(mask) || BlockFlags.isAllowForUseBlockBelowBitSet(mask)) { + return blockData; + } + } + } + + return bundle.nextRandomProximityBlock(random, y); + } + + private boolean shouldObfuscate(ObfuscationRequest request, Chunk chunk, State state, int x, int y, int z) { + return isAdjacentBlockOccluding(request, chunk, state, x, y + 1, z) + && isAdjacentBlockOccluding(request, chunk, state, x, y - 1, z) + && isAdjacentBlockOccluding(request, chunk, state, x + 1, y, z) + && isAdjacentBlockOccluding(request, chunk, state, x - 1, y, z) + && isAdjacentBlockOccluding(request, chunk, state, x, y, z + 1) + && isAdjacentBlockOccluding(request, chunk, state, x, y, z - 1); + } + + private boolean isAdjacentBlockOccluding(ObfuscationRequest request, Chunk chunk, State state, int x, int y, int z) { + int blockId = getBlockId(request, chunk, x, y, z); + if (registryAccessor.isOccluding(blockId)) { + return true; + } + + if (registryAccessor.isLava(blockId)) { + int aboveBlockId = getBlockId(request, chunk, x, y + 1, z); + state.isLava = registryAccessor.isLava(aboveBlockId); + return state.isLava; + } + + return false; + } + + private int getBlockId(ObfuscationRequest request, Chunk chunk, int x, int y, int z) { + if (y >= chunk.world().maxBuildHeight() || y < chunk.world().minBuildHeight()) { + return 0; + } + + int blockId = chunk.getBlockState(x, y, z); + if (blockId == -1) { + blockId = request.getBlockState(x, y, z); + } + + return blockId; + } + + private static class State { + + public boolean isLava = false; + + public void reset() { + this.isLava = false; + } + } +} \ No newline at end of file diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationRequest.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationRequest.java new file mode 100644 index 00000000..cce17292 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationRequest.java @@ -0,0 +1,44 @@ +package dev.imprex.orebfuscator.obfuscation; + +import java.util.Arrays; +import java.util.Objects; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import dev.imprex.orebfuscator.interop.ChunkPacketAccessor; +import dev.imprex.orebfuscator.interop.PlayerAccessor; +import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.util.ChunkDirection; + +@NullMarked +public record ObfuscationRequest( + WorldAccessor world, + PlayerAccessor player, + ChunkPacketAccessor packet, + ChunkAccessor @Nullable [] neighborChunks) { + + @SuppressWarnings("ConstantConditions") + public ObfuscationRequest { + Objects.requireNonNull(world); + Objects.requireNonNull(player); + Objects.requireNonNull(packet); + + if (neighborChunks != null && neighborChunks.length != 4) { + throw new IllegalArgumentException("Expected 4 neighboring chunks but got " + neighborChunks.length); + } else if (neighborChunks != null && Arrays.stream(neighborChunks).anyMatch(Objects::isNull)) { + throw new IllegalArgumentException("Neighboring chunks must not be null"); + } + } + + public ObfuscationRequest withNeighbors(ChunkAccessor[] neighborChunks) { + return new ObfuscationRequest(world, player, packet, Objects.requireNonNull(neighborChunks)); + } + + public int getBlockState(int x, int y, int z) { + if (neighborChunks != null) { + ChunkDirection direction = ChunkDirection.fromPosition(packet, x, z); + return neighborChunks[direction.ordinal()].getBlockState(x, y, z); + } + return 0; + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationResponse.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationResponse.java new file mode 100644 index 00000000..d16f7acc --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/obfuscation/ObfuscationResponse.java @@ -0,0 +1,18 @@ +package dev.imprex.orebfuscator.obfuscation; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.player.ProximityBlock; +import dev.imprex.orebfuscator.util.BlockPos; + +@NullMarked +public record ObfuscationResponse(byte[] data, Set blockEntities, List proximityBlocks) { + + public ObfuscationResponse { + Objects.requireNonNull(data); + Objects.requireNonNull(blockEntities); + Objects.requireNonNull(proximityBlocks); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/OrebfuscatorPlayer.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/OrebfuscatorPlayer.java new file mode 100644 index 00000000..c87537b4 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/OrebfuscatorPlayer.java @@ -0,0 +1,127 @@ +package dev.imprex.orebfuscator.player; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import dev.imprex.orebfuscator.config.api.AdvancedConfig; +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.interop.PlayerAccessor; +import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.util.EntityPose; + +@NullMarked +public class OrebfuscatorPlayer { + + private final AdvancedConfig config; + private final PlayerAccessor player; + + private final AtomicReference<@Nullable WorldAccessor> world = new AtomicReference<>(); + private final Map chunks = new ConcurrentHashMap<>(); + + private volatile long latestUpdateTimestamp = System.currentTimeMillis(); + private volatile EntityPose location = EntityPose.ZERO; + + public OrebfuscatorPlayer(OrebfuscatorCore orebfuscator, PlayerAccessor player) { + this.config = orebfuscator.config().advanced(); + this.player = player; + } + + /** + * Returns true if the last proximity update is longer ago then the configured proximity player interval (default 5s) + * or if the players location since the last update change according to the given rotation boolean and the + * {@link OrebfuscatorPlayer#isLocationSimilar isLocationSimilar} method. + * + * @param rotation passed to the isLocationSimilar method + * @return true if a proximity update is needed + */ + public boolean needsProximityUpdate(boolean rotation) { + if (!player.isAlive()) { + return false; + } + + long timestamp = System.currentTimeMillis(); + if (this.config.hasProximityPlayerCheckInterval() + && timestamp - this.latestUpdateTimestamp > this.config.proximityPlayerCheckInterval()) { + + // always update location + latestUpdateTimestamp on update + this.location = player.pose(); + this.latestUpdateTimestamp = timestamp; + + return true; + } + + EntityPose location = player.pose(); + if (isLocationSimilar(rotation, this.location, location)) { + return false; + } + + // always update location + latestUpdateTimestamp on update + this.location = location; + this.latestUpdateTimestamp = timestamp; + + return true; + } + + /** + * Returns true if the worlds are the same and the distance between the locations is less then 0.5. If the rotation + * boolean is set this method also check if the yaw changed less then 5deg and the pitch less then 2.5deg. + * + * @param rotation should rotation be checked + * @param a the first location + * @param b the second location + * @return if the locations are similar + */ + private static boolean isLocationSimilar(boolean rotation, EntityPose a, EntityPose b) { + // check if world changed + if (!Objects.equals(a.world(), b.world())) { + return false; + } + + // check if len(xyz) changed less then 0.5 blocks + if (a.distanceSquared(b) > 0.25) { + return false; + } + + // check if rotation changed less then 5deg yaw or 2.5deg pitch + if (rotation && (Math.abs(a.rotY() - b.rotY()) > 5 || Math.abs(a.rotX() - b.rotX()) > 2.5)) { + return false; + } + + return true; + } + + public void clearChunks() { + WorldAccessor world = player.world(); + if (!Objects.equals(this.world.getAndSet(world), world)) { + this.chunks.clear(); + } + } + + public void addChunk(WorldAccessor world, int chunkX, int chunkZ, List blocks) { + if (Objects.equals(this.world.getAcquire(), world)) { + long key = ChunkAccessor.chunkCoordsToLong(chunkX, chunkZ); + this.chunks.put(key, new OrebfuscatorPlayerChunk(chunkX, chunkZ, blocks)); + } + } + + public @Nullable OrebfuscatorPlayerChunk getChunk(WorldAccessor world, int chunkX, int chunkZ) { + if (Objects.equals(this.world.getAcquire(), world)) { + long key = ChunkAccessor.chunkCoordsToLong(chunkX, chunkZ); + return this.chunks.get(key); + } else { + return null; + } + } + + public void removeChunk(WorldAccessor world, int chunkX, int chunkZ) { + if (Objects.equals(this.world.getAcquire(), world)) { + long key = ChunkAccessor.chunkCoordsToLong(chunkX, chunkZ); + this.chunks.remove(key); + } + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/OrebfuscatorPlayerChunk.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/OrebfuscatorPlayerChunk.java new file mode 100644 index 00000000..1ec300fe --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/OrebfuscatorPlayerChunk.java @@ -0,0 +1,118 @@ +package dev.imprex.orebfuscator.player; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.util.BlockPos; + +@NullMarked +public class OrebfuscatorPlayerChunk { + + private static final int FLAG_DELETED = 0x80; + private static final int FLAG_LAVA_OBFUSCATED = 0x01; + + private final int chunkX; + private final int chunkZ; + + private int proximitySize; + private int[] proximityBlocks; + private byte[] proximityFlags; + + public OrebfuscatorPlayerChunk(int chunkX, int chunkZ, List proximityBlocks) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + + this.proximitySize = proximityBlocks.size(); + this.proximityBlocks = new int[proximityBlocks.size()]; + this.proximityFlags = new byte[proximityBlocks.size()]; + + for (int i = 0; i < proximityBlocks.size(); i++) { + var block = proximityBlocks.get(i); + this.proximityBlocks[i] = block.blockPos().toSectionPos(); + this.proximityFlags[i] = (byte) (block.lavaObfuscated() ? FLAG_LAVA_OBFUSCATED : 0x00); + } + } + + public boolean isEmpty() { + return this.proximitySize <= 0; + } + + public ProximityIterator proximityIterator() { + return new ProximityItr(); + } + + public interface ProximityIterator extends Iterator, AutoCloseable { + + void close(); + } + + private class ProximityItr implements ProximityIterator { + + private final int x = chunkX << 4; + private final int z = chunkZ << 4; + + private int cursor; + private int removeCursor = -1; + private int deleteCount; + + @Override + public boolean hasNext() { + return cursor < proximitySize; + } + + @Override + public ProximityBlock next() { + if (cursor >= proximitySize) { + throw new NoSuchElementException(); + } + + int sectionPos = proximityBlocks[removeCursor = cursor]; + int flags = proximityFlags[cursor++]; + + var blockPos = BlockPos.fromSectionPos(x, z, sectionPos); + return new ProximityBlock(blockPos, (flags & FLAG_LAVA_OBFUSCATED) != 0); + } + + @Override + public void remove() { + if (removeCursor < 0) { + throw new IllegalStateException(); + } + + // remove entry + final int index = removeCursor; + int flags = proximityFlags[index]; + if ((flags & FLAG_DELETED) != 0) { + throw new IllegalStateException("Already deleted!"); + } + + // update cursor positions + removeCursor = -1; + + proximityFlags[index] |= FLAG_DELETED; + deleteCount++; + } + + @Override + public void close() { + if (deleteCount > 0) { + int newSize = Math.max(0, proximitySize - deleteCount); + int[] newProximityBlocks = new int[newSize]; + byte[] newProximityFlags = new byte[newSize]; + + int newIndex = 0; + for (int oldIndex = 0; newIndex < newSize && oldIndex < proximitySize; oldIndex++) { + if ((proximityFlags[oldIndex] & FLAG_DELETED) == 0) { + newProximityBlocks[newIndex] = proximityBlocks[oldIndex]; + newProximityFlags[newIndex++] = proximityFlags[oldIndex]; + } + } + + proximitySize = newSize; + proximityBlocks = newProximityBlocks; + proximityFlags = newProximityFlags; + } + } + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/ProximityBlock.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/ProximityBlock.java new file mode 100644 index 00000000..89effafa --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/player/ProximityBlock.java @@ -0,0 +1,18 @@ +package dev.imprex.orebfuscator.player; + +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.util.BlockPos; + +@NullMarked +public record ProximityBlock(BlockPos blockPos, boolean lavaObfuscated) { + + private static final byte FLAG_LAVA_OBFUSCATED = 0x01; + + public ProximityBlock(BlockPos blockPos, byte flags) { + this(blockPos, (flags & FLAG_LAVA_OBFUSCATED) == FLAG_LAVA_OBFUSCATED); + } + + public byte flags() { + return lavaObfuscated ? FLAG_LAVA_OBFUSCATED : 0x00; + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximityRayCaster.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximityRayCaster.java new file mode 100644 index 00000000..6f2285a2 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximityRayCaster.java @@ -0,0 +1,113 @@ +package dev.imprex.orebfuscator.proximity; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import dev.imprex.orebfuscator.interop.RegistryAccessor; +import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.util.BlockPos; +import dev.imprex.orebfuscator.util.EntityPose; +import dev.imprex.orebfuscator.util.QuickMaths; +import java.util.Map; +import java.util.HashMap; + +public class ProximityRayCaster { + + private final RegistryAccessor registry; + private final WorldAccessor level; + private final boolean onlyCheckCenter; + + private final Map chunks = new HashMap<>(); + + public ProximityRayCaster(RegistryAccessor registry, WorldAccessor level) { + this.registry = registry; + this.level = level; + this.onlyCheckCenter = level.config().proximity().rayCastCheckOnlyCheckCenter(); + } + + public boolean isVisible(EntityPose origin, BlockPos target) { + if (this.onlyCheckCenter) { + return canRayPass(origin, target, 0.5, 0.5, 0.5); + } + + // midfaces (6) + return canRayPass(origin, target, 0.0, 0.5, 0.5) + || canRayPass(origin, target, 0.5, 0.0, 0.5) + || canRayPass(origin, target, 0.5, 0.5, 0.0) + || canRayPass(origin, target, 0.5, 1.0, 0.5) + || canRayPass(origin, target, 0.5, 0.5, 1.0) + || canRayPass(origin, target, 1.0, 0.5, 0.5) + + // corners (8) + || canRayPass(origin, target, 0.0, 0.0, 0.0) + || canRayPass(origin, target, 1.0, 0.0, 0.0) + || canRayPass(origin, target, 0.0, 1.0, 0.0) + || canRayPass(origin, target, 1.0, 1.0, 0.0) + || canRayPass(origin, target, 0.0, 0.0, 1.0) + || canRayPass(origin, target, 1.0, 0.0, 1.0) + || canRayPass(origin, target, 0.0, 1.0, 1.0) + || canRayPass(origin, target, 1.0, 1.0, 1.0); + } + + private boolean canRayPass(EntityPose origin, BlockPos target, double offsetX, double offsetY, double offsetZ) { + double tx = target.x() + offsetX; + double ty = target.y() + offsetY; + double tz = target.z() + offsetZ; + + double dx = origin.x() - tx; + double dy = origin.y() - ty; + double dz = origin.z() - tz; + + double maxAbs = Math.max(Math.abs(dx), Math.max(Math.abs(dy), Math.abs(dz))); + // on top / inside + if (maxAbs < 1) { + return true; + } + + // step in "dominant-axis" units + dx /= maxAbs; + dy /= maxAbs; + dz /= maxAbs; + + // our current position + double cx = origin.x(); + double cy = origin.y(); + double cz = origin.z(); + + // position of current block + int x, y, z; + + for (int steps = (int) Math.ceil(maxAbs); steps > 0; steps--) { + // move from origin toward target + cx -= dx; + cy -= dy; + cz -= dz; + + x = (int) QuickMaths.floor(cx); + y = (int) QuickMaths.floor(cy); + z = (int) QuickMaths.floor(cz); + + // check if we reached our target block + if (x == target.x() && y == target.y() && z == target.z()) { + return true; + } + + int blockId = this.getBlockState(x, y, z); + // fail on first hit, this ray is "blocked" + if (registry.isOccluding(blockId)) { + return false; + } + } + + return true; + } + + private int getBlockState(int x, int y, int z) { + int chunkX = x >> 4; + int chunkZ = z >> 4; + + long key = ChunkAccessor.chunkCoordsToLong(chunkX, chunkZ); + + return chunks + .computeIfAbsent(key, k -> level.getChunkNow(chunkX, chunkZ)) + .getBlockState(x, y, z); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximitySystem.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximitySystem.java new file mode 100644 index 00000000..54fc5e8a --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximitySystem.java @@ -0,0 +1,104 @@ +package dev.imprex.orebfuscator.proximity; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import dev.imprex.orebfuscator.config.api.AdvancedConfig; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.interop.PlayerAccessor; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.statistics.ObfuscationStatistics; +import dev.imprex.orebfuscator.util.concurrent.OrebfuscatorExecutor; + +public class ProximitySystem implements Runnable { + + private final OrebfuscatorCore orebfuscator; + private final ObfuscationStatistics statistics; + private final OrebfuscatorExecutor executor; + + private final int workerCount; + private final int defaultBucketSize; + private final long checkInterval; + + private final ProximityWorker worker; + + public ProximitySystem(OrebfuscatorCore orebfuscator) { + this.orebfuscator = orebfuscator; + this.statistics = orebfuscator.statistics().obfuscation; + this.executor = orebfuscator.executor(); + + AdvancedConfig advancedConfig = orebfuscator.config().advanced(); + this.workerCount = advancedConfig.threads(); + this.defaultBucketSize = advancedConfig.proximityDefaultBucketSize(); + this.checkInterval = TimeUnit.MILLISECONDS.toNanos(advancedConfig.proximityThreadCheckInterval()); + + this.worker = new ProximityWorker(orebfuscator); + } + + public void start() { + this.executor.schedule(this, this.checkInterval, TimeUnit.NANOSECONDS); + } + + @Override + public void run() { + long processStart = System.nanoTime(); + process().whenComplete((v, throwable) -> { + if (throwable != null) { + OfcLogger.error("An error occurred while running proximity worker", throwable); + } + + if (this.executor.isShutdown()) { + return; + } + + long processTime = System.nanoTime() - processStart; + this.statistics.proximityProcess.add(processTime); + + // check if we have enough time to sleep + long waitTime = Math.max(0, this.checkInterval - processTime); + long waitMillis = TimeUnit.NANOSECONDS.toMillis(waitTime); + + if (waitMillis > 0) { + // measure wait time + this.statistics.proximityWait.add(TimeUnit.MILLISECONDS.toNanos(waitMillis)); + this.executor.schedule(this, waitMillis, TimeUnit.MILLISECONDS); + } else { + this.executor.execute(this); + } + }); + } + + private CompletableFuture process() { + var players = this.orebfuscator.players(); + if (players.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + + // get player count and derive max bucket size for each thread + int playerCount = players.size(); + int maxBucketSize = Math.max(this.defaultBucketSize, (int) Math.ceil((float) playerCount / this.workerCount)); + + // calculate bucket + int bucketCount = (int) Math.ceil((float) playerCount / maxBucketSize); + int bucketSize = (int) Math.ceil((float) playerCount / (float) bucketCount); + + var pendingFutures = new CompletableFuture[bucketCount]; + Iterator iterator = players.iterator(); + + // create buckets and fill queue + for (int index = 0; index < bucketCount; index++) { + List bucket = new ArrayList<>(); + + // fill bucket until bucket full or no players remain + for (int size = 0; size < bucketSize && iterator.hasNext(); size++) { + bucket.add(iterator.next()); + } + + pendingFutures[index] = CompletableFuture.runAsync(() -> this.worker.process(bucket), this.executor); + } + + return CompletableFuture.allOf(pendingFutures); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximityWorker.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximityWorker.java new file mode 100644 index 00000000..aea8a1aa --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/proximity/ProximityWorker.java @@ -0,0 +1,174 @@ +package dev.imprex.orebfuscator.proximity; + +import java.util.ArrayList; +import java.util.List; +import org.joml.FrustumIntersection; +import org.joml.Quaternionf; +import dev.imprex.orebfuscator.PermissionRequirements; +import dev.imprex.orebfuscator.config.OrebfuscatorConfig; +import dev.imprex.orebfuscator.config.api.ProximityConfig; +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.interop.PlayerAccessor; +import dev.imprex.orebfuscator.interop.RegistryAccessor; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.player.OrebfuscatorPlayer; +import dev.imprex.orebfuscator.player.OrebfuscatorPlayerChunk; +import dev.imprex.orebfuscator.player.ProximityBlock; +import dev.imprex.orebfuscator.util.BlockPos; +import dev.imprex.orebfuscator.util.EntityPose; + +public class ProximityWorker { + + private final OrebfuscatorConfig config; + private final RegistryAccessor registry; + + public ProximityWorker(OrebfuscatorCore orebfuscator) { + this.config = orebfuscator.config(); + this.registry = orebfuscator.registry(); + } + + private boolean shouldIgnorePlayer(PlayerAccessor player) { + if (player.hasPermission(PermissionRequirements.BYPASS)) { + return true; + } + + return player.isSpectator() && this.config.general().ignoreSpectator(); + } + + protected void process(List players) { + for (PlayerAccessor player : players) { + try { + this.process(player); + } catch (Exception e) { + OfcLogger.error(e); + } + } + } + + private void process(PlayerAccessor player) { + if (this.shouldIgnorePlayer(player)) { + return; + } + + var world = player.world(); + + // check if level has enabled proximity config + ProximityConfig proximityConfig = world.config().proximity(); + if (proximityConfig == null || !proximityConfig.isEnabled()) { + return; + } + + // frustum culling and ray casting both need rotation changes + boolean needsRotation = proximityConfig.frustumCullingEnabled() || proximityConfig.rayCastCheckEnabled(); + + // check if player changed location since last time + OrebfuscatorPlayer orebfuscatorPlayer = player.orebfuscatorPlayer(); + if (!orebfuscatorPlayer.needsProximityUpdate(needsRotation)) { + return; + } + + int distance = proximityConfig.distance(); + int distanceSquared = distance * distance; + + List updateBlocks = new ArrayList<>(); + EntityPose eyeLocation = player.eyePose(); + + // create frustum planes if culling is enabled + FrustumIntersection frustum = + proximityConfig.frustumCullingEnabled() + ? new FrustumIntersection(proximityConfig.frustumCullingProjectionMatrix() + .rotate(new Quaternionf() + .rotateX((float) Math.toRadians(eyeLocation.rotX())) + .rotateY((float) Math.toRadians(eyeLocation.rotY() + 180))) + .translate((float) -eyeLocation.x(), (float) -eyeLocation.y(), (float) -eyeLocation.z()), false) + : null; + + EntityPose location = player.pose(); + int minChunkX = (location.blockX() - distance) >> 4; + int maxChunkX = (location.blockX() + distance) >> 4; + int minChunkZ = (location.blockZ() - distance) >> 4; + int maxChunkZ = (location.blockZ() + distance) >> 4; + + ChunkAccessor playerChunk = world.getChunkNow(location.blockX() >> 4, location.blockZ() >> 4); + int eyeBlockId = playerChunk.getBlockState(location.blockX(), eyeLocation.blockY(), location.blockZ()); + boolean isInLava = this.registry.isLava(eyeBlockId); + + double lavaDistance = player.lavaFogDistance(); + double lavaDistanceSquared = lavaDistance * lavaDistance; + + ProximityRayCaster rayCaster = proximityConfig.rayCastCheckEnabled() + ? new ProximityRayCaster(registry, world) : null; + + for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { + for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { + + OrebfuscatorPlayerChunk chunk = orebfuscatorPlayer.getChunk(world, chunkX, chunkZ); + if (chunk == null) { + continue; + } + + try (var iterator = chunk.proximityIterator()) { + while (iterator.hasNext()) { + ProximityBlock proximityBlock = iterator.next(); + BlockPos blockPos = proximityBlock.blockPos(); + + // skip lava obfuscated if not in lava + // TODO: the current deobfuscation would only deobfuscate lava if the neighboring block is a hiddenBlock + if (proximityBlock.lavaObfuscated() && !isInLava) { + continue; + } + + double compareDistanceSquared; + double blockDistanceSquared; + if (proximityBlock.lavaObfuscated()) { + blockDistanceSquared = blockPos.distanceSquared(eyeLocation.x(), eyeLocation.y(), eyeLocation.z()); + compareDistanceSquared = lavaDistanceSquared; + } else { + blockDistanceSquared = blockPos.distanceSquared(location.x(), location.y(), location.z()); + compareDistanceSquared = distanceSquared; + } + + // check if block is in range + if (blockDistanceSquared > compareDistanceSquared) { + continue; + } + + // do frustum culling check + if (proximityConfig.frustumCullingEnabled() + && blockDistanceSquared > proximityConfig.frustumCullingMinDistanceSquared()) { + + // check if block AABB is inside frustum + int result = frustum.intersectAab(blockPos.x(), blockPos.y(), blockPos.z(), blockPos.x() + 1, + blockPos.y() + 1, blockPos.z() + 1); + + // block is outside + if (result != FrustumIntersection.INSIDE && result != FrustumIntersection.INTERSECT) { + continue; + } + } + + // do ray cast check + if (rayCaster != null && !rayCaster.isVisible(eyeLocation, blockPos)) { + continue; + } + + // block is visible and needs update + iterator.remove(); + updateBlocks.add(blockPos); + } + } + + if (chunk.isEmpty()) { + orebfuscatorPlayer.removeChunk(world, chunkX, chunkZ); + } + } + } + + player.runForPlayer(() -> { + if (player.isAlive() && player.world().equals(world)) { + player.sendBlockUpdates(updateBlocks); + } + }); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/Reflector.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/Reflector.java index 6e823248..4036d84a 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/Reflector.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/Reflector.java @@ -8,7 +8,6 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; import dev.imprex.orebfuscator.reflect.accessor.Accessors; import dev.imprex.orebfuscator.reflect.accessor.ConstructorAccessor; import dev.imprex.orebfuscator.reflect.accessor.FieldAccessor; @@ -16,17 +15,20 @@ import dev.imprex.orebfuscator.reflect.predicate.ConstructorPredicate; import dev.imprex.orebfuscator.reflect.predicate.FieldPredicate; import dev.imprex.orebfuscator.reflect.predicate.MethodPredicate; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class Reflector { public static Reflector of(Class target) { return new Reflector(target); } - private final @NotNull Class target; - private @NotNull Class recursiveUntil; + private final Class target; + private Class recursiveUntil; - private Reflector(@NotNull Class target) { + private Reflector(Class target) { this.target = Objects.requireNonNull(target); this.recursiveUntil = target; } @@ -35,7 +37,7 @@ public Reflector recursive() { return this.recursiveUntil(Object.class); } - public Reflector recursiveUntil(Class recursiveUntil) { + public Reflector recursiveUntil(@Nullable Class recursiveUntil) { this.recursiveUntil = recursiveUntil != null ? recursiveUntil : this.target; @@ -68,35 +70,29 @@ private Stream get(Function, T[]> getter) { return stream; } - @NotNull public Stream constructor(Predicate> predicate) { Stream> stream = get(Class::getDeclaredConstructors); return stream.filter(predicate).map(Accessors::wrap); } - @NotNull public ConstructorPredicate constructor() { return new ConstructorPredicate(this::constructor, this::className); } - @NotNull public Stream field(Predicate predicate) { Stream stream = get(Class::getDeclaredFields); return stream.filter(predicate).map(Accessors::wrap); } - @NotNull public FieldPredicate field() { return new FieldPredicate(this::field, this::className); } - @NotNull public Stream method(Predicate predicate) { Stream stream = get(Class::getDeclaredMethods); return stream.filter(predicate).map(Accessors::wrap); } - @NotNull public MethodPredicate method() { return new MethodPredicate(this::method, this::className); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/Accessors.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/Accessors.java index 2fc4b450..3bee61e4 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/Accessors.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/Accessors.java @@ -11,8 +11,9 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Objects; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; +@NullMarked public final class Accessors { private static final Lookup LOOKUP = MethodHandles.lookup(); @@ -23,7 +24,7 @@ public final class Accessors { private Accessors() { } - public static @NotNull ConstructorAccessor wrap(@NotNull Constructor constructor) { + public static ConstructorAccessor wrap(Constructor constructor) { return create(constructor, () -> { MethodHandle methodHandle = LOOKUP.unreflectConstructor(constructor); methodHandle = generifyExecutable(methodHandle, false, true); @@ -32,14 +33,14 @@ private Accessors() { }); } - public static @NotNull FieldAccessor wrap(@NotNull Field field) { + public static FieldAccessor wrap(Field field) { return create(field, () -> { MethodHandle getter = LOOKUP.unreflectGetter(field); MethodHandle setter = null; try { setter = LOOKUP.unreflectSetter(field); - } catch (IllegalAccessException e) { + } catch (IllegalAccessException ignored) { } if (Modifier.isStatic(field.getModifiers())) { @@ -54,7 +55,7 @@ private Accessors() { }); } - public static @NotNull MethodAccessor wrap(@NotNull Method method) { + public static MethodAccessor wrap(Method method) { return create(method, () -> { MethodHandle methodHandle = LOOKUP.unreflect(method); methodHandle = generifyExecutable(methodHandle, Modifier.isStatic(method.getModifiers()), false); @@ -63,8 +64,8 @@ private Accessors() { }); } - private static @NotNull TAccessor create( - @NotNull TMember member, @NotNull AccessorFactory factory) { + private static TAccessor create( + TMember member, AccessorFactory factory) { Objects.requireNonNull(member); @SuppressWarnings("deprecation") diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultConstrutorAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultConstrutorAccessor.java index 58b41fb7..532895fd 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultConstrutorAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultConstrutorAccessor.java @@ -1,11 +1,11 @@ package dev.imprex.orebfuscator.reflect.accessor; -import org.jetbrains.annotations.NotNull; import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; +import org.jspecify.annotations.NonNull; -record DefaultConstructorAccessor(@NotNull Constructor member, @NotNull MethodHandle methodHandle) implements +record DefaultConstructorAccessor(@NonNull Constructor member, @NonNull MethodHandle methodHandle) implements ConstructorAccessor { @Override @@ -18,7 +18,7 @@ public Object invoke(Object... args) { } @Override - public @NotNull Constructor member() { + public @NonNull Constructor member() { return member; } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultFieldAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultFieldAccessor.java index c91dab56..60c8838a 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultFieldAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultFieldAccessor.java @@ -2,10 +2,10 @@ import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; -record DefaultFieldAccessor(@NotNull Field member, @NotNull MethodHandle getterHandle, +record DefaultFieldAccessor(@NonNull Field member, @NonNull MethodHandle getterHandle, @Nullable MethodHandle setterHandle) implements FieldAccessor { @Override @@ -36,7 +36,7 @@ public void set(Object instance, Object value) { } @Override - public @NotNull Field member() { + public @NonNull Field member() { return this.member; } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultMethodAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultMethodAccessor.java index 940bb2f5..1faff2db 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultMethodAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultMethodAccessor.java @@ -2,9 +2,9 @@ import java.lang.invoke.MethodHandle; import java.lang.reflect.Method; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; -record DefaultMethodAccessor(@NotNull Method member, @NotNull MethodHandle methodHandle) implements +record DefaultMethodAccessor(@NonNull Method member, @NonNull MethodHandle methodHandle) implements MethodAccessor { @Override @@ -17,7 +17,7 @@ public Object invoke(Object instance, Object... args) { } @Override - public @NotNull Method member() { + public @NonNull Method member() { return member; } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MemberAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MemberAccessor.java index ce43b857..ac61b941 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MemberAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MemberAccessor.java @@ -1,10 +1,10 @@ package dev.imprex.orebfuscator.reflect.accessor; -import org.jetbrains.annotations.NotNull; import java.lang.reflect.Member; +import org.jspecify.annotations.NonNull; public interface MemberAccessor { - @NotNull TMember member(); + @NonNull TMember member(); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessor.java index 11198eb8..1b07f00f 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessor.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessor.java @@ -5,5 +5,4 @@ public interface MethodAccessor extends MemberAccessor { Object invoke(Object target, Object... args); - } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicate.java index d3c6722a..b9546bc3 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicate.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicate.java @@ -4,34 +4,36 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.OptionalInt; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import dev.imprex.orebfuscator.reflect.accessor.MemberAccessor; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked abstract sealed class AbstractExecutablePredicate< TThis extends AbstractExecutablePredicate, TAccessor extends MemberAccessor, TExecutable extends Executable > extends AbstractMemberPredicate permits ConstructorPredicate, MethodPredicate { - private final @NotNull List exceptionClass = new ArrayList<>(); - private final @NotNull List parameterClass = new ArrayList<>(); + private final List exceptionClass = new ArrayList<>(); + private final List parameterClass = new ArrayList<>(); private int parameterCount = -1; public AbstractExecutablePredicate( - @NotNull Function> producer, - @NotNull Supplier error) { + Function> producer, + Supplier error) { super(producer, error); } @Override - public boolean test(@NotNull TExecutable executable) { + public boolean test(TExecutable executable) { return super.test(executable) && IndexedClassMatcher.all(executable.getExceptionTypes(), exceptionClass) && IndexedClassMatcher.all(executable.getParameterTypes(), parameterClass) @@ -39,7 +41,7 @@ public boolean test(@NotNull TExecutable executable) { } @Override - void requirements(@NotNull RequirementCollector collector) { + void requirements(RequirementCollector collector) { super.requirements(collector); if (!exceptionClass.isEmpty()) { @@ -53,96 +55,99 @@ void requirements(@NotNull RequirementCollector collector) { } } - public @NotNull TThis exception(@NotNull ClassPredicate matcher) { + public TThis exception(ClassPredicate matcher) { this.exceptionClass.add(new IndexedClassMatcher(Objects.requireNonNull(matcher))); return instance(); } - public @NotNull TThis exception(@NotNull ClassPredicate matcher, int index) { + public TThis exception(ClassPredicate matcher, int index) { this.exceptionClass.add(new IndexedClassMatcher(Objects.requireNonNull(matcher), index)); return instance(); } - public @NotNull ClassPredicate.Builder exception() { + public ClassPredicate.Builder exception() { return new ClassPredicate.Builder<>(this::exception); } - public @NotNull ClassPredicate.Builder exception(int index) { + public ClassPredicate.Builder exception(int index) { return new ClassPredicate.Builder<>(m -> this.exception(m, index)); } - public @NotNull TThis parameter(@NotNull ClassPredicate matcher) { + public TThis parameter(ClassPredicate matcher) { this.parameterClass.add(new IndexedClassMatcher(Objects.requireNonNull(matcher))); return instance(); } - public @NotNull TThis parameter(@NotNull ClassPredicate matcher, int index) { + public TThis parameter(ClassPredicate matcher, int index) { this.parameterClass.add(new IndexedClassMatcher(Objects.requireNonNull(matcher), index)); return instance(); } - public @NotNull ClassPredicate.Builder parameter() { + public ClassPredicate.Builder parameter() { return new ClassPredicate.Builder<>(this::parameter); } - public @NotNull ClassPredicate.Builder parameter(int index) { + public ClassPredicate.Builder parameter(int index) { return new ClassPredicate.Builder<>(m -> this.parameter(m, index)); } - public @NotNull TThis parameterCount(int parameterCount) { + public TThis parameterCount(int parameterCount) { this.parameterCount = parameterCount; return instance(); } - private record IndexedClassMatcher(@NotNull ClassPredicate matcher, @Nullable Integer index) implements + private record IndexedClassMatcher(ClassPredicate matcher, OptionalInt index) implements Comparable { - private static boolean all(@NotNull Class[] classArray, @NotNull List classMatchers) { + private static boolean all(Class[] classArray, List classMatchers) { return classMatchers.stream().allMatch(matcher -> matcher.matches(classArray)); } - private static String toString(@NotNull List classMatchers) { - return classMatchers.stream() - .sorted() - .map(IndexedClassMatcher::toString) + private static String toString(List classMatchers) { + return classMatchers.stream().sorted().map(IndexedClassMatcher::toString) .collect(Collectors.joining(",\n ", "{\n ", "\n }")); } - public IndexedClassMatcher(@NotNull ClassPredicate matcher) { - this(matcher, null); + public IndexedClassMatcher(ClassPredicate matcher) { + this(matcher, OptionalInt.empty()); } - public boolean matches(@NotNull Class[] classArray) { - if (index() == null) { + public IndexedClassMatcher(ClassPredicate matcher, int index) { + this(matcher, OptionalInt.of(index)); + } + + public boolean matches(Class[] classArray) { + if (index.isEmpty()) { for (Class entry : classArray) { - if (matcher().test(entry)) { + if (matcher.test(entry)) { return true; } } return false; } - return index() < classArray.length && matcher().test(classArray[index()]); + int i = index.getAsInt(); + return i < classArray.length && matcher.test(classArray[i]); } @Override - public int compareTo(@NotNull IndexedClassMatcher other) { - if (this.index == null && other.index == null) { + public int compareTo(IndexedClassMatcher other) { + if (this.index.isEmpty() && other.index.isEmpty()) { return 0; } - if (this.index == null) { + if (this.index.isEmpty()) { return -1; } - if (other.index == null) { + if (other.index.isEmpty()) { return 1; } - return this.index.compareTo(other.index); + return Integer.compare(this.index.getAsInt(), other.index.getAsInt()); } @Override - public @NotNull String toString() { - String key = index() == null ? "" : index().toString(); - return String.format("%s=%s", key, matcher().requirement()); + public String toString() { + String key = index.isEmpty() ? "" : Integer.toString(index.getAsInt()); + return String.format("%s=%s", key, matcher.requirement()); } } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicate.java index 5cff71ac..9565c622 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicate.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicate.java @@ -1,5 +1,6 @@ package dev.imprex.orebfuscator.reflect.predicate; +import dev.imprex.orebfuscator.reflect.accessor.MemberAccessor; import java.lang.reflect.Member; import java.lang.reflect.Modifier; import java.util.Objects; @@ -8,22 +9,20 @@ import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Stream; - import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import dev.imprex.orebfuscator.reflect.accessor.MemberAccessor; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked abstract sealed class AbstractMemberPredicate< TThis extends AbstractMemberPredicate, TAccessor extends MemberAccessor, TMember extends Member > implements Predicate permits AbstractExecutablePredicate, FieldPredicate { - private final @NotNull Function> producer; - private final @NotNull Supplier error; + private final Function> producer; + private final Supplier error; private int requiredModifiers; private int bannedModifiers; @@ -32,14 +31,14 @@ abstract sealed class AbstractMemberPredicate< private @Nullable ClassPredicate declaringClass; public AbstractMemberPredicate( - @NotNull Function> producer, - @NotNull Supplier error) { + Function> producer, + Supplier error) { this.producer = producer; this.error = error; } @Override - public boolean test(@NotNull TMember member) { + public boolean test(TMember member) { int modifiers = member.getModifiers(); return (modifiers & requiredModifiers) == requiredModifiers && (modifiers & bannedModifiers) == 0 @@ -48,7 +47,7 @@ public boolean test(@NotNull TMember member) { && (declaringClass == null || declaringClass.test(member.getDeclaringClass())); } - void requirements(@NotNull RequirementCollector collector) { + void requirements(RequirementCollector collector) { if (requiredModifiers != 0) { collector.collect("requiredModifiers", Modifier.toString(requiredModifiers)); } @@ -66,95 +65,95 @@ void requirements(@NotNull RequirementCollector collector) { } } - private @NotNull IllegalArgumentException requirementException() { + private IllegalArgumentException requirementException() { var collector = new RequirementCollector(error.get()); requirements(collector); return new IllegalArgumentException(collector.get()); } - protected abstract @NotNull TThis instance(); + protected abstract TThis instance(); - public @NotNull TThis requireModifier(@MagicConstant(flagsFromClass = Modifier.class) int modifiers) { + public TThis requireModifier(@MagicConstant(flagsFromClass = Modifier.class) int modifiers) { this.requiredModifiers |= modifiers; return instance(); } - public @NotNull TThis requirePublic() { + public TThis requirePublic() { return requireModifier(Modifier.PUBLIC); } - public @NotNull TThis requireProtected() { + public TThis requireProtected() { return requireModifier(Modifier.PROTECTED); } - public @NotNull TThis requirePrivate() { + public TThis requirePrivate() { return requireModifier(Modifier.PRIVATE); } - public @NotNull TThis requireStatic() { + public TThis requireStatic() { return requireModifier(Modifier.STATIC); } - public @NotNull TThis requireFinal() { + public TThis requireFinal() { return requireModifier(Modifier.FINAL); } - public @NotNull TThis banModifier(@MagicConstant(flagsFromClass = Modifier.class) int modifiers) { + public TThis banModifier(@MagicConstant(flagsFromClass = Modifier.class) int modifiers) { this.bannedModifiers |= modifiers; return instance(); } - public @NotNull TThis banPublic() { + public TThis banPublic() { return banModifier(Modifier.PUBLIC); } - public @NotNull TThis banProtected() { + public TThis banProtected() { return banModifier(Modifier.PROTECTED); } - public @NotNull TThis banPrivate() { + public TThis banPrivate() { return banModifier(Modifier.PRIVATE); } - public @NotNull TThis banStatic() { + public TThis banStatic() { return banModifier(Modifier.STATIC); } - public @NotNull TThis banFinal() { + public TThis banFinal() { return banModifier(Modifier.FINAL); } - public @NotNull TThis includeSynthetic() { + public TThis includeSynthetic() { this.includeSynthetic = true; return instance(); } - public @NotNull TThis nameRegex(@NotNull Pattern pattern) { + public TThis nameRegex(Pattern pattern) { this.name = Objects.requireNonNull(pattern); return instance(); } - public @NotNull TThis nameIs(@NotNull String name) { + public TThis nameIs(String name) { String pattern = Pattern.quote(Objects.requireNonNull(name)); return nameRegex(Pattern.compile(pattern)); } - public @NotNull TThis nameIsIgnoreCase(@NotNull String name) { + public TThis nameIsIgnoreCase(String name) { String pattern = Pattern.quote(Objects.requireNonNull(name)); return nameRegex(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); } - public @NotNull TThis declaringClass(@NotNull ClassPredicate matcher) { + public TThis declaringClass(ClassPredicate matcher) { this.declaringClass = Objects.requireNonNull(matcher); return instance(); } - public @NotNull ClassPredicate.Builder declaringClass() { + public ClassPredicate.Builder declaringClass() { return new ClassPredicate.Builder<>(this::declaringClass); } @Contract(pure = true) - public @NotNull Stream stream() { + public Stream stream() { return producer.apply(instance()); } @@ -164,7 +163,7 @@ void requirements(@NotNull RequirementCollector collector) { } @Contract(pure = true) - public @NotNull TAccessor getOrThrow(int index) { + public TAccessor getOrThrow(int index) { return stream().skip(index).findFirst().orElseThrow(this::requirementException); } @@ -174,18 +173,18 @@ void requirements(@NotNull RequirementCollector collector) { } @Contract(pure = true) - public @NotNull TAccessor firstOrThrow() { + public TAccessor firstOrThrow() { return stream().findFirst().orElseThrow(this::requirementException); } @Contract(pure = true) - public @Nullable TAccessor find(@NotNull Predicate predicate) { + public @Nullable TAccessor find(Predicate predicate) { return stream().filter(accessor -> predicate.test(accessor.member())) .findFirst().orElse(null); } @Contract(pure = true) - public @NotNull TAccessor findOrThrow(@NotNull Predicate predicate) { + public TAccessor findOrThrow(Predicate predicate) { return stream().filter(accessor -> predicate.test(accessor.member())) .findFirst().orElseThrow(this::requirementException); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicate.java index 09e22367..601c80fc 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicate.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicate.java @@ -6,70 +6,55 @@ import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; +@NullMarked public interface ClassPredicate extends Predicate> { - boolean test(@NotNull Class type); + boolean test(Class type); - @NotNull String requirement(); - class Builder { + record Builder(Function returnFunction) { - private final Function returnFunction; - - public Builder(Function returnFunction) { - this.returnFunction = returnFunction; - } - - @NotNull - public TParent is(@NotNull Class type) { + public TParent is(Class type) { return returnFunction.apply(new IsClassPredicate(type)); } - @NotNull - public TParent superOf(@NotNull Class type) { + public TParent superOf(Class type) { return returnFunction.apply(new SuperClassPredicate(type)); } - @NotNull - public TParent subOf(@NotNull Class type) { + public TParent subOf(Class type) { return returnFunction.apply(new SubClassPredicate(type)); } - @NotNull - public TParent any(@NotNull Set> types) { + public TParent any(Set> types) { return returnFunction.apply(new AnyClassPredicate(types)); } - @NotNull - public TParent any(@NotNull Class... types) { + public TParent any(Class... types) { return any(Set.of(types)); } - @NotNull - public TParent regex(@NotNull Pattern pattern) { + public TParent regex(Pattern pattern) { return returnFunction.apply(new RegexClassPredicate(pattern)); } } - class IsClassPredicate implements ClassPredicate { - - private final @NotNull Class expected; + record IsClassPredicate(Class expected) implements ClassPredicate { - public IsClassPredicate(@NotNull Class expected) { - this.expected = Objects.requireNonNull(expected); + public IsClassPredicate { + Objects.requireNonNull(expected); } @Override - public boolean test(@NotNull Class type) { + public boolean test(Class type) { Objects.requireNonNull(type); return this.expected.equals(type); } - @NotNull @Override public String requirement() { return String.format("{is %s}", this.expected.getTypeName()); @@ -81,22 +66,19 @@ public boolean equals(Object obj) { } } - class SuperClassPredicate implements ClassPredicate { + record SuperClassPredicate(Class expected) implements ClassPredicate { - private final @NotNull Class expected; - - public SuperClassPredicate(@NotNull Class expected) { - this.expected = Objects.requireNonNull(expected); + public SuperClassPredicate { + Objects.requireNonNull(expected); } @Override - public boolean test(@NotNull Class type) { + public boolean test(Class type) { Objects.requireNonNull(type); return type.isAssignableFrom(this.expected); } - @NotNull @Override public String requirement() { return String.format("{super-class-of %s}", this.expected.getTypeName()); @@ -108,22 +90,19 @@ public boolean equals(Object obj) { } } - class SubClassPredicate implements ClassPredicate { - - private final @NotNull Class expected; + record SubClassPredicate(Class expected) implements ClassPredicate { - public SubClassPredicate(@NotNull Class expected) { - this.expected = Objects.requireNonNull(expected); + public SubClassPredicate { + Objects.requireNonNull(expected); } @Override - public boolean test(@NotNull Class type) { + public boolean test(Class type) { Objects.requireNonNull(type); return this.expected.isAssignableFrom(type); } - @NotNull @Override public String requirement() { return String.format("{sub-class-of %s}", this.expected.getTypeName()); @@ -135,22 +114,19 @@ public boolean equals(Object obj) { } } - class AnyClassPredicate implements ClassPredicate { + record AnyClassPredicate(Set> expected) implements ClassPredicate { - private final @NotNull Set> expected; - - public AnyClassPredicate(@NotNull Set> expected) { - this.expected = Objects.requireNonNull(expected); + public AnyClassPredicate { + Objects.requireNonNull(expected); } @Override - public boolean test(@NotNull Class type) { + public boolean test(Class type) { Objects.requireNonNull(type); return this.expected.contains(type); } - @NotNull @Override public String requirement() { return String.format("{any %s}", @@ -163,22 +139,19 @@ public boolean equals(Object obj) { } } - class RegexClassPredicate implements ClassPredicate { - - private final @NotNull Pattern expected; + record RegexClassPredicate(Pattern expected) implements ClassPredicate { - public RegexClassPredicate(@NotNull Pattern expected) { - this.expected = Objects.requireNonNull(expected); + public RegexClassPredicate { + Objects.requireNonNull(expected); } @Override - public boolean test(@NotNull Class type) { + public boolean test(Class type) { Objects.requireNonNull(type); return this.expected.matcher(type.getTypeName()).matches(); } - @NotNull @Override public String requirement() { return String.format("{regex %s}", this.expected); diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ConstructorPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ConstructorPredicate.java index d1e89d3a..7b35cbe1 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ConstructorPredicate.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ConstructorPredicate.java @@ -5,21 +5,22 @@ import java.util.function.Supplier; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; import dev.imprex.orebfuscator.reflect.accessor.ConstructorAccessor; +import org.jspecify.annotations.NullMarked; +@NullMarked public final class ConstructorPredicate extends AbstractExecutablePredicate> { public ConstructorPredicate( - @NotNull Function> producer, - @NotNull Supplier className) { + Function> producer, + Supplier className) { super(producer, () -> String.format("Can't find constructor in class %s matching: ", className.get())); } @Override - protected @NotNull ConstructorPredicate instance() { + protected ConstructorPredicate instance() { return this; } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicate.java index 6af1db37..80e877ee 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicate.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicate.java @@ -6,27 +6,28 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public final class FieldPredicate extends AbstractMemberPredicate { private @Nullable ClassPredicate type; public FieldPredicate( - @NotNull Function> producer, - @NotNull Supplier className) { + Function> producer, + Supplier className) { super(producer, () -> String.format("Can't find field in class %s matching: ", className.get())); } @Override - public boolean test(@NotNull Field field) { + public boolean test(Field field) { return super.test(field) && (type == null || type.test(field.getType())); } @Override - void requirements(@NotNull RequirementCollector collector) { + void requirements(RequirementCollector collector) { super.requirements(collector); if (type != null) { @@ -34,17 +35,17 @@ void requirements(@NotNull RequirementCollector collector) { } } - public @NotNull FieldPredicate type(@NotNull ClassPredicate matcher) { + public FieldPredicate type(ClassPredicate matcher) { this.type = Objects.requireNonNull(matcher); return this; } - public @NotNull ClassPredicate.Builder type() { + public ClassPredicate.Builder type() { return new ClassPredicate.Builder<>(this::type); } @Override - protected @NotNull FieldPredicate instance() { + protected FieldPredicate instance() { return this; } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicate.java index 462d6e83..06d9d606 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicate.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicate.java @@ -6,27 +6,28 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public final class MethodPredicate extends AbstractExecutablePredicate { private @Nullable ClassPredicate returnType; public MethodPredicate( - @NotNull Function> producer, - @NotNull Supplier className) { + Function> producer, + Supplier className) { super(producer, () -> String.format("Can't find constructor in class %s matching: ", className.get())); } @Override - public boolean test(@NotNull Method method) { + public boolean test(Method method) { return super.test(method) && (returnType == null || returnType.test(method.getReturnType())); } @Override - void requirements(@NotNull RequirementCollector collector) { + void requirements(RequirementCollector collector) { super.requirements(collector); if (returnType != null) { @@ -34,17 +35,17 @@ void requirements(@NotNull RequirementCollector collector) { } } - public @NotNull MethodPredicate returnType(@NotNull ClassPredicate matcher) { + public MethodPredicate returnType(ClassPredicate matcher) { this.returnType = Objects.requireNonNull(matcher); return this; } - public @NotNull ClassPredicate.Builder returnType() { + public ClassPredicate.Builder returnType() { return new ClassPredicate.Builder<>(this::returnType); } @Override - protected @NotNull MethodPredicate instance() { + protected MethodPredicate instance() { return this; } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/RequirementCollector.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/RequirementCollector.java index d1a91e77..1384360e 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/RequirementCollector.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/RequirementCollector.java @@ -2,26 +2,25 @@ import java.util.Objects; import java.util.StringJoiner; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; +@NullMarked class RequirementCollector { private final StringJoiner entries; - public RequirementCollector(@NotNull String prefix) { + public RequirementCollector(String prefix) { this.entries = new StringJoiner(",\n", prefix + "{\n", "\n}"); } - @NotNull - public RequirementCollector collect(@NotNull String name) { + public RequirementCollector collect(String name) { Objects.requireNonNull(name); entries.add(" " + name); return this; } - @NotNull - public RequirementCollector collect(@NotNull String name, @NotNull Object value) { + public RequirementCollector collect(String name, Object value) { Objects.requireNonNull(name); Objects.requireNonNull(value); @@ -29,7 +28,6 @@ public RequirementCollector collect(@NotNull String name, @NotNull Object value) return this; } - @NotNull public String get() { return entries.toString(); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/CacheStatistics.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/CacheStatistics.java new file mode 100644 index 00000000..c6ac8c3d --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/CacheStatistics.java @@ -0,0 +1,150 @@ +package dev.imprex.orebfuscator.statistics; + +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.LongSupplier; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.util.RollingAverage; +import dev.imprex.orebfuscator.util.RollingTimer; + +public class CacheStatistics implements StatisticsSource { + + private final CacheConfig config; + + private final AtomicLong cacheHitCountMemory = new AtomicLong(0); + private final AtomicLong cacheHitCountDisk = new AtomicLong(0); + private final AtomicLong cacheMissCount = new AtomicLong(0); + + private final AtomicLong memoryCacheByteSize = new AtomicLong(0); + private LongSupplier memoryCacheEntryCount = () -> 0; + + private LongSupplier diskCacheQueueLength = () -> 0; + private final RollingAverage diskCacheReadBytes = new RollingAverage(4096); + private final RollingAverage diskCacheWriteBytes = new RollingAverage(4096); + public final RollingTimer diskCacheWaitTime = new RollingTimer(4096); + public final RollingTimer diskCacheReadTime = new RollingTimer(4096); + public final RollingTimer diskCacheWriteTime = new RollingTimer(4096); + + public CacheStatistics(Config config) { + this.config = config.cache(); + } + + public void onCacheHitMemory() { + this.cacheHitCountMemory.incrementAndGet(); + } + + public void onCacheHitDisk() { + this.cacheHitCountDisk.incrementAndGet(); + } + + public void onCacheMiss() { + this.cacheMissCount.incrementAndGet(); + } + + public void onCacheSizeChange(int delta) { + this.memoryCacheByteSize.addAndGet(delta); + } + + public void setMemoryCacheEntryCount(LongSupplier supplier) { + this.memoryCacheEntryCount = Objects.requireNonNull(supplier); + } + + public void setDiskCacheQueueLength(LongSupplier supplier) { + this.diskCacheQueueLength = Objects.requireNonNull(supplier); + } + + public void onDiskCacheRead(long bytes) { + this.diskCacheReadBytes.add(bytes); + } + + public void onDiskCacheWrite(long bytes) { + this.diskCacheWriteBytes.add(bytes); + } + + public void add(StringJoiner joiner) { + long cacheHitCountMemory = this.cacheHitCountMemory.get(); + long cacheHitCountDisk = this.cacheHitCountDisk.get(); + long cacheMissCount = this.cacheMissCount.get(); + long totalCount = cacheHitCountMemory + cacheHitCountDisk + cacheMissCount; + + double memoryHitRate = 0.0d; + double diskHitRate = 0.0d; + double missRate = 1.0d; + if (totalCount > 0) { + memoryHitRate = (double) cacheHitCountMemory / totalCount; + diskHitRate = (double) cacheHitCountDisk / totalCount; + missRate = 1d - (memoryHitRate + diskHitRate); + } + + joiner.add(String.format(" - cacheHitRate (memory/disk/miss): %s / %s / %s", + percent(memoryHitRate), percent(diskHitRate), percent(missRate))); + + long memoryCacheByteSize = this.memoryCacheByteSize.get(); + long memoryCacheEntryCount = this.memoryCacheEntryCount.getAsLong(); + + long memoryCacheBytesPerEntry = 0; + if (memoryCacheByteSize > 0) { + memoryCacheBytesPerEntry = memoryCacheByteSize / memoryCacheEntryCount; + } + + joiner.add(String.format(" - memoryCache (count/bytesPerEntry): %s / %s ", + memoryCacheEntryCount, bytes(memoryCacheBytesPerEntry))); + + if (this.config.enableDiskCache()) { + long diskCacheQueueLength = this.diskCacheQueueLength.getAsLong(); + + joiner.add(String.format(" - diskCache (queue): %s", diskCacheQueueLength)); + + long diskCacheWaitTime = (long) this.diskCacheWaitTime.average(); + long diskCacheReadTime = (long) this.diskCacheReadTime.average(); + long diskCacheWriteTime = (long) this.diskCacheWriteTime.average(); + + joiner.add(String.format(" - diskCacheTime (wait/read/write): %s / %s / %s", + time(diskCacheWaitTime), time(diskCacheReadTime), time(diskCacheWriteTime))); + + long diskCacheReadBytes = (long) this.diskCacheReadBytes.average(); + long diskCacheWriteBytes = (long) this.diskCacheWriteBytes.average(); + + double diskCacheReadTimeSeconds = 1d; + if (diskCacheReadTime > 0) { + diskCacheReadTimeSeconds = (double) diskCacheReadTime / (double) TimeUnit.SECONDS.toNanos(1); + } + long diskCacheReadBytesPerSecond = Math.round((double) diskCacheReadBytes / diskCacheReadTimeSeconds); + + double diskCacheWriteTimeSeconds = 1d; + if (diskCacheWriteTime > 0) { + diskCacheWriteTimeSeconds = (double) diskCacheWriteTime / (double) TimeUnit.SECONDS.toNanos(1); + } + long diskCacheWriteBytesPerSecond = Math.round((double) diskCacheWriteBytes / diskCacheWriteTimeSeconds); + + joiner.add(String.format(" - diskCacheThroughput (read/write): %s/s / %s/s", + bytes(diskCacheReadBytesPerSecond), bytes(diskCacheWriteBytesPerSecond))); + + joiner.add(String.format(" - diskCacheTaskSize (read/write): %s / %s", + bytes(diskCacheReadBytes), bytes(diskCacheWriteBytes))); + } + } + + @Override + public void debug(BiConsumer consumer) { + consumer.accept("cacheHitCountMemory", Long.toString(cacheHitCountMemory.get())); + consumer.accept("cacheHitCountDisk", Long.toString(cacheHitCountDisk.get())); + consumer.accept("cacheMissCount", Long.toString(cacheMissCount.get())); + + consumer.accept("memoryCacheByteSize", Long.toString(memoryCacheByteSize.get())); + consumer.accept("memoryCacheEntryCount", Long.toString(memoryCacheEntryCount.getAsLong())); + + consumer.accept("diskCacheQueueLength", Long.toString(diskCacheQueueLength.getAsLong())); + consumer.accept("diskCacheReadBytes", diskCacheReadBytes.debugLong(this::bytes)); + consumer.accept("diskCacheWriteBytes", diskCacheWriteBytes.debugLong(this::bytes)); + consumer.accept("diskCacheWaitTime", diskCacheWaitTime.debugLong(this::time)); + consumer.accept("diskCacheReadTime", diskCacheReadTime.debugLong(this::time)); + consumer.accept("diskCacheWriteTime", diskCacheWriteTime.debugLong(this::time)); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/InjectorStatistics.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/InjectorStatistics.java new file mode 100644 index 00000000..786b0586 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/InjectorStatistics.java @@ -0,0 +1,59 @@ +package dev.imprex.orebfuscator.statistics; + +import java.util.Map; +import java.util.StringJoiner; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import dev.imprex.orebfuscator.util.RollingAverage; +import dev.imprex.orebfuscator.util.RollingTimer; + +public class InjectorStatistics implements StatisticsSource { + + public final RollingTimer pipelineDelayTotal = new RollingTimer(4096); + public final RollingTimer pipelineDelayCache = new RollingTimer(4096); + public final RollingTimer pipelineDelayNeighbors = new RollingTimer(4096); + public final RollingTimer pipelineDelayProcessor = new RollingTimer(4096); + + public final RollingTimer injectorDelaySync = new RollingTimer(4096); + public final RollingAverage injectorBatchSize = new RollingAverage(4096); + + public final RollingTimer packetDelayAny = new RollingTimer(4096); + public final RollingTimer packetDelayChunk = new RollingTimer(4096); + + @Override + public void add(StringJoiner joiner) { + long pipeline = (long) this.pipelineDelayTotal.average(); + long cache = (long) this.pipelineDelayCache.average(); + long neighbors = (long) this.pipelineDelayNeighbors.average(); + long processor = (long) this.pipelineDelayProcessor.average(); + + joiner.add(String.format(" - pipelineDelay (t/c/n/p): %s / %s / %s / %s", + time(pipeline), time(cache), time(neighbors), time(processor))); + + long sync = (long) this.injectorDelaySync.average(); + double batchSize = this.injectorBatchSize.average(); + + joiner.add(String.format(" - injector (syncDelay/batchSize): %s / %.2f", + time(sync), batchSize)); + + long any = (long) this.packetDelayAny.average(); + long chunk = (long) this.packetDelayChunk.average(); + + joiner.add(String.format(" - packetDelay (any/chunk): %s / %s", + time(any), time(chunk))); + } + + @Override + public void debug(BiConsumer consumer) { + consumer.accept("pipelineDelayTotal", this.pipelineDelayTotal.debugLong(this::time)); + consumer.accept("pipelineDelayCache", this.pipelineDelayCache.debugLong(this::time)); + consumer.accept("pipelineDelayNeighbors", this.pipelineDelayNeighbors.debugLong(this::time)); + consumer.accept("pipelineDelayProcessor", this.pipelineDelayProcessor.debugLong(this::time)); + + consumer.accept("injectorDelaySync", this.injectorDelaySync.debugLong(this::time)); + consumer.accept("injectorBatchSize", this.injectorBatchSize.debugLong(this::time)); + + consumer.accept("packetDelayAny", this.packetDelayAny.debugLong(this::time)); + consumer.accept("packetDelayChunk", this.packetDelayChunk.debugLong(this::time)); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/ObfuscationStatistics.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/ObfuscationStatistics.java new file mode 100644 index 00000000..623bfc0a --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/ObfuscationStatistics.java @@ -0,0 +1,82 @@ +package dev.imprex.orebfuscator.statistics; + +import java.util.Map; +import java.util.StringJoiner; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import dev.imprex.orebfuscator.util.RollingAverage; +import dev.imprex.orebfuscator.util.RollingTimer; + +public class ObfuscationStatistics implements StatisticsSource { + + public final RollingTimer debofuscation = new RollingTimer(4096); + + public final RollingTimer executorWaitTime = new RollingTimer(4096); + public final RollingTimer executorUtilization = new RollingTimer(4096); + + public final RollingTimer proximityWait = new RollingTimer(4096); + public final RollingTimer proximityProcess = new RollingTimer(4096); + + public final RollingAverage missingNeighboringChunks = new RollingAverage(4096); + + public final RollingAverage originalChunkSize = new RollingAverage(4096); + public final RollingAverage obfuscatedChunkSize = new RollingAverage(4096); + + @Override + public void add(StringJoiner joiner) { + long debofuscation = (long) this.debofuscation.average(); + + joiner.add(String.format(" - debofuscation: %s", + time(debofuscation))); + + long executorWaitTime = (long) this.executorWaitTime.average(); + double executorUtilization = this.executorUtilization.average(); + + joiner.add(String.format(" - executor (wait/utilization): %s / %s", + time(executorWaitTime), percent(executorUtilization))); + + double proximityWait = this.proximityWait.average(); + double proximityProcess = this.proximityProcess.average(); + double proximityTotalTime = proximityWait + proximityProcess; + + double proximityUtilization = 0; + if (proximityTotalTime > 0) { + proximityUtilization = (double) proximityProcess / proximityTotalTime; + } + + joiner.add(String.format(" - proximity utilization: %s", + percent(proximityUtilization))); + + double missingNeighboringChunks = this.missingNeighboringChunks.average(); + + joiner.add(String.format(" - missingNeighbors: %s", + faction(missingNeighboringChunks))); + + long originalChunkSize = (long) this.originalChunkSize.average(); + long obfuscatedChunkSize = (long) this.obfuscatedChunkSize.average(); + + double chunkSizeRatio = 1; + if (originalChunkSize > 0) { + chunkSizeRatio = (double) obfuscatedChunkSize / originalChunkSize; + } + + joiner.add(String.format(" - chunk size (org/obf/rat): %s / %s / %s ", + bytes(originalChunkSize), bytes(obfuscatedChunkSize), percent(chunkSizeRatio))); + } + + @Override + public void debug(BiConsumer consumer) { + consumer.accept("debofuscation", this.debofuscation.debugLong(this::time)); + + consumer.accept("executorWaitTime", this.executorWaitTime.debugLong(this::time)); + consumer.accept("executorUtilization", this.executorUtilization.debugDouble(this::percent)); + + consumer.accept("proximityWait", this.proximityWait.debugLong(this::time)); + consumer.accept("proximityProcess", this.proximityProcess.debugDouble(this::percent)); + + consumer.accept("missingNeighboringChunks", this.missingNeighboringChunks.debugDouble(this::faction)); + + consumer.accept("originalChunkSize", this.originalChunkSize.debugLong(this::bytes)); + consumer.accept("obfuscatedChunkSize", this.obfuscatedChunkSize.debugLong(this::bytes)); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/OrebfuscatorStatistics.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/OrebfuscatorStatistics.java new file mode 100644 index 00000000..11d9052d --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/OrebfuscatorStatistics.java @@ -0,0 +1,21 @@ +package dev.imprex.orebfuscator.statistics; + +import dev.imprex.orebfuscator.config.api.Config; + +public class OrebfuscatorStatistics { + + public final CacheStatistics cache; + public final InjectorStatistics injector = new InjectorStatistics(); + public final ObfuscationStatistics obfuscation = new ObfuscationStatistics(); + + public OrebfuscatorStatistics(Config config, StatisticsRegistry registry) { + this.cache = new CacheStatistics(config); + + if (config.cache().enabled()) { + registry.register("cache", this.cache); + } + + registry.register("injector", this.injector); + registry.register("obfuscation", this.obfuscation); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/StatisticsRegistry.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/StatisticsRegistry.java new file mode 100644 index 00000000..29cf9fcd --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/StatisticsRegistry.java @@ -0,0 +1,53 @@ +package dev.imprex.orebfuscator.statistics; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import com.google.gson.JsonObject; + +public class StatisticsRegistry { + + private final Map sources = new HashMap<>(); + + public void register(String name, StatisticsSource source) { + if (sources.containsKey(name)) { + throw new IllegalArgumentException("Duplicate statistics name: " + name); + } + + this.sources.put(name, source); + } + + public String format() { + var joiner = new StringJoiner("\n", "Here are some useful statistics:\n", ""); + + for (var source : sources.values()) { + source.add(joiner); + } + + return joiner.toString(); + } + + public String debug() { + return entries().stream() + .sorted(Comparator.comparing(Map.Entry::getKey)) + .map(entry -> String.format(" - %s: %s", entry.getKey(), entry.getValue())) + .collect(Collectors.joining("\n")); + } + + public Set> entries() { + var entries = new LinkedHashMap(); + + for (var source : sources.values()) { + source.debug(entries::put); + } + + return entries.entrySet(); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/StatisticsSource.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/StatisticsSource.java new file mode 100644 index 00000000..6288f966 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/statistics/StatisticsSource.java @@ -0,0 +1,44 @@ +package dev.imprex.orebfuscator.statistics; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.StringJoiner; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public interface StatisticsSource { + + default String faction(double value) { + return String.format("%.3f", value); + } + + default String percent(double value) { + return String.format("%.2f%%", value * 100); + } + + default String time(long time) { + if (time > 1000_000L) { + return String.format("%.1fms", time / 1000_000d); + } else if (time > 1000L) { + return String.format("%.1fµs", time / 1000d); + } else { + return String.format("%dns", time); + } + } + + default String bytes(long bytes) { + if (bytes > 1073741824L) { + return String.format("%.1f GiB", bytes / 1073741824d); + } else if (bytes > 1048576L) { + return String.format("%.1f MiB", bytes / 1048576d); + } else if (bytes > 1024L) { + return String.format("%.1f KiB", bytes / 1024d); + } else { + return String.format("%d B", bytes); + } + } + + void add(StringJoiner joiner); + + void debug(BiConsumer consumer); +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/AbstractHttpService.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/AbstractHttpService.java similarity index 75% rename from orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/AbstractHttpService.java rename to orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/AbstractHttpService.java index 1ab196ce..d168b638 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/AbstractHttpService.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/AbstractHttpService.java @@ -1,4 +1,4 @@ -package net.imprex.orebfuscator.util; +package dev.imprex.orebfuscator.util; import java.io.IOException; import java.io.InputStreamReader; @@ -10,15 +10,12 @@ import java.net.http.HttpResponse.BodySubscribers; import java.nio.charset.StandardCharsets; import java.util.Optional; - -import org.bukkit.plugin.PluginDescriptionFile; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import org.jspecify.annotations.NullMarked; -import dev.imprex.orebfuscator.util.Version; -import net.imprex.orebfuscator.Orebfuscator; - +@NullMarked public abstract class AbstractHttpService { public static final Gson GSON = new GsonBuilder().setPrettyPrinting() @@ -29,9 +26,8 @@ public abstract class AbstractHttpService { protected final String userAgent; - public AbstractHttpService(Orebfuscator orebfuscator) { - PluginDescriptionFile plugin = orebfuscator.getDescription(); - this.userAgent = String.format("Imprex-Development/%s/%s", plugin.getName(), plugin.getVersion()); + public AbstractHttpService(OrebfuscatorCore orebfuscator) { + this.userAgent = String.format("Imprex-Development/%s/%s", orebfuscator.name(), orebfuscator.orebfuscatorVersion()); } protected HttpRequest.Builder request(String url) { @@ -48,7 +44,6 @@ protected static BodyHandler> optionalJson(Class target) { } catch (IOException e) { throw new UncheckedIOException("I/O while reading JSON", e); } - }) - : BodySubscribers.replacing(Optional.empty()); + }) : BodySubscribers.replacing(Optional.empty()); } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockPos.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockPos.java index 8e46b483..c1337add 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockPos.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockPos.java @@ -1,5 +1,8 @@ package dev.imprex.orebfuscator.util; +import org.jspecify.annotations.NullMarked; + +@NullMarked public record BlockPos(int x, int y, int z) implements Comparable { // from net.minecraft.core.BlockPos @@ -24,6 +27,10 @@ public BlockPos add(int x, int y, int z) { return x == 0 && y == 0 && z == 0 ? this : new BlockPos(this.x + x, this.y + y, this.z + z); } + public BlockPos add(BlockPos other) { + return this.add(other.x(), other.y(), other.z()); + } + public double distanceSquared(double x, double y, double z) { double dx = this.x - x; double dy = this.y - y; @@ -43,7 +50,7 @@ public static BlockPos fromLong(long value) { } public int toSectionPos() { - return (this.x & 0xF) << 12 | (this.y & 0xFFF) << 0 | (this.z & 0xF) << 16; + return (this.x & 0xF) << 12 | (this.y & 0xFFF) | (this.z & 0xF) << 16; } public static BlockPos fromSectionPos(int x, int z, int sectionPos) { @@ -63,9 +70,4 @@ public int compareTo(BlockPos other) { } return this.y - other.y; } - - @Override - public String toString() { - return "BlockPos [x=" + x + ", y=" + y + ", z=" + z + "]"; - } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockProperties.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockProperties.java index 30a0561c..667e3479 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockProperties.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockProperties.java @@ -4,7 +4,10 @@ import java.util.Objects; import java.util.Set; import com.google.common.collect.ImmutableList; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class BlockProperties { public static Builder builder(NamespacedKey key) { @@ -17,7 +20,7 @@ public static Builder builder(NamespacedKey key) { private BlockProperties(Builder builder) { this.key = builder.key; - this.defaultBlockState = builder.defaultBlockState; + this.defaultBlockState = Objects.requireNonNull(builder.defaultBlockState); this.blockStates = ImmutableList.copyOf(builder.blockStates); } @@ -59,11 +62,11 @@ public static class Builder { private final NamespacedKey key; - private BlockStateProperties defaultBlockState; + private @Nullable BlockStateProperties defaultBlockState; private final Set blockStates = new HashSet<>(); private Builder(NamespacedKey key) { - this.key = key; + this.key = Objects.requireNonNull(key); } public Builder withBlockState(BlockStateProperties blockState) { @@ -76,7 +79,7 @@ public Builder withBlockState(BlockStateProperties blockState) { // check for multiple default blocks if (this.defaultBlockState != null) { throw new IllegalStateException( - String.format("multiple default block states for block: %s", blockState.getId(), key)); + String.format("multiple default block states for block: %s", key)); } this.defaultBlockState = blockState; @@ -88,7 +91,7 @@ public Builder withBlockState(BlockStateProperties blockState) { public BlockProperties build() { Objects.requireNonNull(this.defaultBlockState, "missing default block state for block: " + this.key); - if (this.blockStates.size() == 0) { + if (this.blockStates.isEmpty()) { throw new IllegalStateException("missing block states for block: " + this.key); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockStateProperties.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockStateProperties.java index 9c53c5f3..490252d1 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockStateProperties.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockStateProperties.java @@ -1,5 +1,8 @@ package dev.imprex.orebfuscator.util; +import org.jspecify.annotations.NullMarked; + +@NullMarked public class BlockStateProperties { public static Builder builder(int id) { @@ -9,6 +12,7 @@ public static Builder builder(int id) { private final int id; private final boolean isAir; + private final boolean isLava; private final boolean isOccluding; private final boolean isBlockEntity; private final boolean isDefaultState; @@ -16,6 +20,7 @@ public static Builder builder(int id) { private BlockStateProperties(Builder builder) { this.id = builder.id; this.isAir = builder.isAir; + this.isLava = builder.isLava; this.isOccluding = builder.isOccluding; this.isBlockEntity = builder.isBlockEntity; this.isDefaultState = builder.isDefaultState; @@ -29,6 +34,10 @@ public boolean isAir() { return isAir; } + public boolean isLava() { + return isLava; + } + public boolean isOccluding() { return isOccluding; } @@ -68,6 +77,7 @@ public static class Builder { private final int id; private boolean isAir; + private boolean isLava; private boolean isOccluding; private boolean isBlockEntity; private boolean isDefaultState; @@ -81,6 +91,11 @@ public Builder withIsAir(boolean isAir) { return this; } + public Builder withIsLava(boolean isLava) { + this.isLava = isLava; + return this; + } + public Builder withIsOccluding(boolean isOccluding) { this.isOccluding = isOccluding; return this; diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockTag.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockTag.java index 5769bda4..bf9b7ccb 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockTag.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockTag.java @@ -3,13 +3,14 @@ import java.util.Collections; import java.util.Objects; import java.util.Set; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; -public record BlockTag(@NotNull NamespacedKey key, @NotNull Set blocks) { +@NullMarked +public record BlockTag(NamespacedKey key, Set blocks) { - public BlockTag(@NotNull NamespacedKey key, @NotNull Set blocks) { - this.key = key; - this.blocks = Collections.unmodifiableSet(blocks); + public BlockTag(NamespacedKey key, Set blocks) { + this.key = Objects.requireNonNull(key); + this.blocks = Collections.unmodifiableSet(Objects.requireNonNull(blocks)); } @Override diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkCacheKey.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkCacheKey.java index 2de79156..96ae50c3 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkCacheKey.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkCacheKey.java @@ -1,15 +1,26 @@ package dev.imprex.orebfuscator.util; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.obfuscation.ObfuscationRequest; -public record ChunkCacheKey(@NotNull String world, int x, int z) { +@NullMarked +public record ChunkCacheKey(String world, int x, int z) { - public ChunkCacheKey(@NotNull WorldAccessor world, BlockPos position) { - this(world.getName(), position.x() >> 4, position.z() >> 4); + public ChunkCacheKey(ObfuscationRequest request) { + this(request.world(), request.packet().chunkX(), request.packet().chunkZ()); } - public ChunkCacheKey(@NotNull WorldAccessor world, int x, int z) { - this(world.getName(), x, z); + public ChunkCacheKey(WorldAccessor world, BlockPos position) { + this(world.name(), position.x() >> 4, position.z() >> 4); + } + + public ChunkCacheKey(WorldAccessor world, int x, int z) { + this(world.name(), x, z); + } + + @Override + public String toString() { + return "[%s, (%s, %s)]".formatted(world, x, z); } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkDirection.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkDirection.java index a9f65cb3..eab3c852 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkDirection.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkDirection.java @@ -1,5 +1,10 @@ package dev.imprex.orebfuscator.util; +import java.util.Objects; +import dev.imprex.orebfuscator.interop.ChunkPacketAccessor; +import org.jspecify.annotations.NullMarked; + +@NullMarked public enum ChunkDirection { NORTH(1, 0), EAST(0, 1), SOUTH(-1, 0), WEST(0, -1); @@ -20,9 +25,11 @@ public int getOffsetZ() { return offsetZ; } - public static ChunkDirection fromPosition(ChunkCacheKey key, int targetX, int targetZ) { - int offsetX = (targetX >> 4) - key.x(); - int offsetZ = (targetZ >> 4) - key.z(); + public static ChunkDirection fromPosition(ChunkPacketAccessor packetAccessor, int targetX, int targetZ) { + Objects.requireNonNull(packetAccessor); + + int offsetX = (targetX >> 4) - packetAccessor.chunkX(); + int offsetZ = (targetZ >> 4) - packetAccessor.chunkZ(); if (offsetX == 1 && offsetZ == 0) { return NORTH; @@ -34,7 +41,7 @@ public static ChunkDirection fromPosition(ChunkCacheKey key, int targetX, int ta return WEST; } - throw new IllegalArgumentException( - String.format("invalid offset (origin: %s, x: %d, z: %d)", key, targetX, targetZ)); + throw new IllegalArgumentException(String.format("invalid offset (chunkX: %d, chunkZ: %d, x: %d, z: %d)", + packetAccessor.chunkX(), packetAccessor.chunkZ(), targetX, targetZ)); } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/ConsoleUtil.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ConsoleUtil.java similarity index 77% rename from orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/ConsoleUtil.java rename to orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ConsoleUtil.java index 7333b96d..7fc58d0f 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/ConsoleUtil.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ConsoleUtil.java @@ -1,11 +1,8 @@ -package net.imprex.orebfuscator.util; +package dev.imprex.orebfuscator.util; import java.util.ArrayList; import java.util.Arrays; import java.util.List; - -import org.bukkit.ChatColor; - import dev.imprex.orebfuscator.logging.LogLevel; import dev.imprex.orebfuscator.logging.OfcLogger; @@ -18,9 +15,9 @@ private ConsoleUtil() { } public static String replaceAnsiColorWithChatColor(String value) { - value = value.replaceAll("\u001B\\[m", ChatColor.RESET.toString()); - value = value.replaceAll("\u001B\\[31;1m", ChatColor.RED.toString()); - value = value.replaceAll("\u001B\\[33;1m", ChatColor.YELLOW.toString()); + value = value.replaceAll("\u001B\\[m", "\u00A7r"); + value = value.replaceAll("\u001B\\[31;1m", "\u00A7c"); + value = value.replaceAll("\u001B\\[33;1m", "\u00A7e"); return value; } @@ -76,7 +73,7 @@ public static Iterable createBox(String... lines) { int totalWidth = width + BOX_PADDING * 2; // create top/bottom lines - String bottomTopLine = repeat('-', totalWidth); + String bottomTopLine = "-".repeat(totalWidth); String topLine = String.format("+%s+", bottomTopLine); String bottomLine = String.format("+%s+", bottomTopLine); @@ -90,10 +87,10 @@ public static Iterable createBox(String... lines) { // center line String leftPadding, rightPadding; if (space % 2 == 0) { - leftPadding = rightPadding = repeat(' ', space / 2); + leftPadding = rightPadding = " ".repeat(space / 2); } else { - leftPadding = repeat(' ', space / 2 + 1); - rightPadding = repeat(' ', space / 2); + leftPadding = " ".repeat(space / 2 + 1); + rightPadding = " ".repeat(space / 2); } box.add(String.format("|%s%s%s|", leftPadding, line, rightPadding)); @@ -102,10 +99,4 @@ public static Iterable createBox(String... lines) { box.add(bottomLine); return box; } - - private static String repeat(char character, int length) { - char[] string = new char[length]; - Arrays.fill(string, character); - return new String(string); - } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/EntityPose.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/EntityPose.java new file mode 100644 index 00000000..ecca6930 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/EntityPose.java @@ -0,0 +1,30 @@ +package dev.imprex.orebfuscator.util; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import dev.imprex.orebfuscator.interop.WorldAccessor; + +@NullMarked +public record EntityPose(@Nullable WorldAccessor world, double x, double y, double z, float rotX, float rotY) { + + public static final EntityPose ZERO = new EntityPose(null, 0, 0, 0, 0, 0); + + public int blockX() { + return QuickMaths.floor(this.x); + } + + public int blockY() { + return QuickMaths.floor(this.y); + } + + public int blockZ() { + return QuickMaths.floor(this.z); + } + + public double distanceSquared(EntityPose other) { + double dx = this.x - other.x; + double dy = this.y - other.y; + double dz = this.z - other.z; + return dx * dx + dy * dy + dz * dz; + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/NamespacedKey.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/NamespacedKey.java index 3a029d3b..2770a075 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/NamespacedKey.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/NamespacedKey.java @@ -1,6 +1,10 @@ package dev.imprex.orebfuscator.util; -import org.jetbrains.annotations.NotNull; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.jspecify.annotations.NullMarked; /** * Represents a String based key which consists of two components - a namespace and a key. @@ -12,141 +16,49 @@ * @author org.bukkit.NamespacedKey from 1.19.4 * */ + +@NullMarked public record NamespacedKey(String namespace, String key) { - /** - * The namespace representing all inbuilt keys. - */ - public static final String MINECRAFT = "minecraft"; + private static final String DEFAULT_NAMESPACE = "minecraft"; - private static boolean isValidNamespaceChar(char c) { - return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-'; - } + private static final Pattern PARSE = Pattern.compile("^(?:(?[a-z0-9._-]+):)?(?[a-z0-9/._-]+)$"); + private static final Predicate VALID_NAMESPACE = Pattern.compile("^[a-z0-9._-]+$").asMatchPredicate(); + private static final Predicate VALID_KEY = Pattern.compile("^[a-z0-9/._-]+$").asMatchPredicate(); - private static boolean isValidKeyChar(char c) { - return isValidNamespaceChar(c) || c == '/'; + public static NamespacedKey parse(String value) { + return tryParse(value).orElseThrow(() -> new IllegalArgumentException( + "Invalid namespaced key. Must be /^([a-z0-9._-]+:)?[a-z0-9/._-]+$/: %s".formatted(value))); } - private static boolean isValidNamespace(String namespace) { - int len = namespace.length(); - if (len == 0) { - return false; - } - - for (int i = 0; i < len; i++) { - if (!isValidNamespaceChar(namespace.charAt(i))) { - return false; - } + public static Optional tryParse(String value) { + Matcher matcher = PARSE.matcher(value); + if (!matcher.find()) { + return Optional.empty(); } - return true; - } - - private static boolean isValidKey(String key) { - int len = key.length(); - if (len == 0) { - return false; - } + String namespace = matcher.group("namespace"); + String key = matcher.group("key"); - for (int i = 0; i < len; i++) { - if (!isValidKeyChar(key.charAt(i))) { - return false; - } + if ("..".equals(namespace)) { + return Optional.empty(); } - return true; + return Optional.of(new NamespacedKey(namespace == null ? DEFAULT_NAMESPACE : namespace, key)); } - /** - * Create a key in a specific namespace. - * - * @param namespace namespace - * @param key key - * @deprecated should never be used by plugins, for internal use only!! - */ - @Deprecated - public NamespacedKey(String namespace, String key) { - if (namespace == null || !isValidNamespace(namespace)) { - throw new IllegalArgumentException(String.format("Invalid namespace. Must be [a-z0-9._-]: %s", namespace)); - } else if (key == null || !isValidKey(key)) { - throw new IllegalArgumentException(String.format("Invalid key. Must be [a-z0-9/._-]: %s", key)); + @SuppressWarnings("ConstantConditions") + public NamespacedKey { + if (namespace == null || namespace.equals("..") || !VALID_NAMESPACE.test(namespace)) { + throw new IllegalArgumentException("Invalid namespace. Must be [a-z0-9._-]: %s".formatted(namespace)); } - - this.namespace = namespace; - this.key = key; - - String string = toString(); - if (string.length() >= 256) { - throw new IllegalArgumentException(String.format("NamespacedKey must be less than 256 characters (%s)", string)); + if (key == null || !VALID_KEY.test(key)) { + throw new IllegalArgumentException("Invalid key. Must be [a-z0-9/._-]: %s".formatted(key)); } } - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final NamespacedKey other = (NamespacedKey) obj; - return this.namespace.equals(other.namespace) && this.key.equals(other.key); - } - @Override public String toString() { return this.namespace + ":" + this.key; } - - /** - * Get a key in the Minecraft namespace. - * - * @param key the key to use - * @return new key in the Minecraft namespace - */ - public static NamespacedKey minecraft(String key) { - return new NamespacedKey(MINECRAFT, key); - } - - - /** - * Get a NamespacedKey from the supplied string. - *

- * The default namespace will be Minecraft's (i.e. {@link #minecraft(String)}). - * - * @return the created NamespacedKey. null if invalid - */ - public static NamespacedKey fromString(@NotNull String string) { - if (string == null || string.isEmpty()) { - throw new IllegalArgumentException("Input string must not be empty or null"); - } - - String[] components = string.split(":", 3); - if (components.length > 2) { - return null; - } - - String key = (components.length == 2) ? components[1] : ""; - if (components.length == 1) { - String value = components[0]; - if (value.isEmpty() || !isValidKey(value)) { - return null; - } - - return minecraft(value); - } else if (components.length == 2 && !isValidKey(key)) { - return null; - } - - String namespace = components[0]; - if (namespace.isEmpty()) { - return minecraft(key); - } - - if (!isValidNamespace(namespace)) { - return null; - } - - return new NamespacedKey(namespace, key); - } } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/MathUtil.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/QuickMaths.java similarity index 81% rename from orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/MathUtil.java rename to orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/QuickMaths.java index 095a8fa8..d285f348 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/MathUtil.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/QuickMaths.java @@ -1,6 +1,6 @@ package dev.imprex.orebfuscator.util; -public class MathUtil { +public class QuickMaths { public static int ceilToPowerOfTwo(int value) { value--; @@ -13,6 +13,11 @@ public static int ceilToPowerOfTwo(int value) { return value; } + public static int floor(double value) { + int i = (int) value; + return value < (double) i ? i - 1 : i; + } + public static int clamp(int value, int min, int max) { return Math.max(min, Math.min(max, value)); } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/RollingAverage.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/RollingAverage.java new file mode 100644 index 00000000..a5486fa2 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/RollingAverage.java @@ -0,0 +1,102 @@ +package dev.imprex.orebfuscator.util; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.DoubleFunction; +import java.util.function.LongFunction; +import org.jspecify.annotations.NullMarked; +import com.google.common.util.concurrent.AtomicDouble; + +@NullMarked +public class RollingAverage { + + private static final VarHandle BUFFER_HANDLE = MethodHandles.arrayElementVarHandle(double[].class); + + private final double[] buffer; + + private final AtomicInteger head = new AtomicInteger(0); + private final AtomicInteger size = new AtomicInteger(0); + + private final AtomicDouble min = new AtomicDouble(Long.MAX_VALUE); + private final AtomicDouble max = new AtomicDouble(Long.MIN_VALUE); + + public RollingAverage(int capacity) { + this.buffer = new double[capacity]; + } + + public void add(double value) { + int index = head.getAndUpdate(h -> (h + 1) % buffer.length); + BUFFER_HANDLE.setRelease(buffer, index, value); + + if (size.get() < buffer.length) { + size.updateAndGet(s -> s < buffer.length ? s + 1 : s); + } + + if (size.get() >= buffer.length) { + min.getAndUpdate(prev -> Math.min(prev, value)); + max.getAndUpdate(prev -> Math.max(prev, value)); + } + } + + public double average() { + int size = this.size.get(); + if (size == 0) { + return 0; + } + + double sum = 0; + for (int i = 0; i < size; i++) { + sum += (double) BUFFER_HANDLE.getAcquire(buffer, i); + } + + return sum / size; + } + + private double percentile(double p) { + int size = this.size.get(); + if (size == 0) { + return 0; + } + + double[] copy = new double[size]; + for (int i = 0; i < size; i++) { + copy[i] = (double) BUFFER_HANDLE.getAcquire(buffer, i); + } + + Arrays.sort(copy); + double rank = p * (size - 1); + int lower = (int) Math.floor(rank); + int upper = (int) Math.ceil(rank); + if (lower == upper) { + return copy[lower]; + } + // Linear interpolation + return copy[lower] + (copy[upper] - copy[lower]) * (rank - lower); + } + + public String debugLong(LongFunction formatter) { + var n = size.get(); + var avg = formatter.apply((long) average()); + var p95 = formatter.apply((long) percentile(0.95)); + var p99 = formatter.apply((long) percentile(0.99)); + var min = formatter.apply((long) this.min.get()); + var max = formatter.apply((long) this.max.get()); + + return String.format("{size=%d, avg=%s, p95=%s, p99=%s, min=%s, max=%s}", n, avg, p95, + p99, min, max); + } + + public String debugDouble(DoubleFunction formatter) { + var n = size.get(); + var avg = formatter.apply(average()); + var p95 = formatter.apply(percentile(0.95)); + var p99 = formatter.apply(percentile(0.99)); + var min = formatter.apply(this.min.get()); + var max = formatter.apply(this.max.get()); + + return String.format("{size=%d, avg=%s, p95=%s, p99=%s, min=%s, max=%s}", n, avg, p95, + p99, min, max); + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/RollingTimer.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/RollingTimer.java new file mode 100644 index 00000000..e4406d39 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/RollingTimer.java @@ -0,0 +1,36 @@ +package dev.imprex.orebfuscator.util; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicBoolean; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class RollingTimer extends RollingAverage { + + public RollingTimer(int capacity) { + super(capacity); + } + + public Instance start() { + return new Instance(); + } + + public class Instance { + + private final long time = System.nanoTime(); + private final AtomicBoolean running = new AtomicBoolean(true); + + private Instance() { + } + + public CompletionStage wrap(CompletionStage completionStage) { + return completionStage.whenComplete((a, b) -> stop()); + } + + public void stop() { + if (this.running.compareAndSet(true, false)) { + add(System.nanoTime() - time); + } + } + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/SimpleCache.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/SimpleCache.java index d5122138..d8ec4bdd 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/SimpleCache.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/SimpleCache.java @@ -3,14 +3,14 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; +import org.jspecify.annotations.NullMarked; /** * Simple cache implementation that removes the oldest element once a certain size is reached */ +@NullMarked public class SimpleCache extends LinkedHashMap { - private static final long serialVersionUID = -2732738355560313649L; - private final int maximumSize; private final Consumer> remove; diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/Version.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/Version.java index 6a0b4b88..8dc5056f 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/Version.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/Version.java @@ -1,6 +1,7 @@ package dev.imprex.orebfuscator.util; import java.io.IOException; +import java.util.Comparator; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; @@ -8,11 +9,14 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -public record Version(int major, int minor, int patch) implements Comparable { +@NullMarked +public record Version(int major, int minor, int patch, @Nullable String suffix) implements Comparable { private static final Pattern VERSION_PATTERN = - Pattern.compile("(?\\d+)(?:\\.(?\\d+))?(?:\\.(?\\d+))?"); + Pattern.compile("^(?\\d+)(?:\\.(?\\d+))?(?:\\.(?\\d+))?(?.+)?$"); public static Version parse(String version) { return tryParse(version) @@ -20,7 +24,7 @@ public static Version parse(String version) { } public static Optional tryParse(String version) { - Matcher matcher = VERSION_PATTERN.matcher(version); + Matcher matcher = VERSION_PATTERN.matcher(version.trim()); if (!matcher.find()) { return Optional.empty(); @@ -34,7 +38,9 @@ public static Optional tryParse(String version) { String patchGroup = matcher.group("patch"); int patch = patchGroup != null ? Integer.parseInt(patchGroup) : 0; - return Optional.of(new Version(major, minor, patch)); + String suffix = matcher.group("suffix"); + + return Optional.of(new Version(major, minor, patch, suffix)); } public boolean isAbove(String version) { @@ -81,12 +87,17 @@ public int compareTo(Version other) { return minor; } - return Integer.compare(this.patch, other.patch); + int patch = Integer.compare(this.patch, other.patch); + if (patch != 0) { + return patch; + } + + return Objects.compare(this.suffix, other.suffix, Comparator.nullsLast(Comparator.naturalOrder())); } @Override public int hashCode() { - return Objects.hash(major, minor, patch); + return Objects.hash(major, minor, patch, suffix); } @Override @@ -97,12 +108,12 @@ public boolean equals(Object obj) { if (!(obj instanceof Version other)) { return false; } - return major == other.major && minor == other.minor && patch == other.patch; + return major == other.major && minor == other.minor && patch == other.patch && Objects.equals(suffix, other.suffix); } @Override public String toString() { - return String.format("%s.%s.%s", this.major, this.minor, this.patch); + return String.format("%s.%s.%s%s", this.major, this.minor, this.patch, Objects.toString(this.suffix, "")); } public static final class Json extends TypeAdapter { diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/WeightedRandom.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/WeightedRandom.java index ac40fd34..68dd6a75 100644 --- a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/WeightedRandom.java +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/WeightedRandom.java @@ -7,7 +7,7 @@ import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; import java.util.random.RandomGenerator; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NullMarked; /** * Weighted random integer sampler using the @@ -35,6 +35,7 @@ * This implementation is immutable and thread-safe for concurrent calls to * {@link #next()} after construction. */ +@NullMarked public final class WeightedRandom { /** @@ -59,7 +60,7 @@ public static Builder builder() { * * @param builder the builder containing values and weights */ - private WeightedRandom(@NotNull Builder builder) { + private WeightedRandom(Builder builder) { double minWeight = Double.POSITIVE_INFINITY; double maxWeight = Double.NEGATIVE_INFINITY; double totalWeight = 0d; @@ -142,22 +143,48 @@ public int next() { return next(ThreadLocalRandom.current()); } + private static double boundedNextDouble(long seed, double bound) { + // Specialize boundedNextDouble for origin == 0, bound > 0 + double r = (seed >>> 11) * 0x1.0p-53; + r = r * bound; + if (r >= bound) // may need to correct a rounding problem + { + r = Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + return r; + } + + private static int boundedNextInt(long seed, int n) { + return ((n & (n - 1)) == 0) + ? (int) (seed & (n - 1)) + : (int) (((seed & 0xFFFF_FFFFL) * (long) n) >>> 32); + } + /** * Samples a random value using the given random generator. * * @param random a {@link RandomGenerator} to use for randomness * @return a sampled integer according to the configured weights */ - public int next(@NotNull RandomGenerator random) { + public int next(RandomGenerator random) { Objects.requireNonNull(random); - int i = random.nextInt(this.n); + long seed = random.nextLong(); + + int i = boundedNextInt(seed, this.n); if (this.allWeightsEqual) { return values[i]; } - int pick = random.nextDouble(totalWeight) < probabilities[i] ? i : alias[i]; + int pick = boundedNextDouble(seed, totalWeight) < probabilities[i] ? i : alias[i]; return values[pick]; +// int i = random.nextInt(this.n); +// if (this.allWeightsEqual) { +// return values[i]; +// } +// +// int pick = random.nextDouble(totalWeight) < probabilities[i] ? i : alias[i]; +// return values[pick]; } /** @@ -187,7 +214,7 @@ public Builder add(int value, double weight) { throw new IllegalArgumentException("Weight has to be greater zero and finite!"); } - this.entries.merge(value, weight, (a, b) -> a + b); + this.entries.merge(value, weight, Double::sum); return this; } diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/concurrent/OrebfuscatorExecutor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/concurrent/OrebfuscatorExecutor.java new file mode 100644 index 00000000..d09fa587 --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/concurrent/OrebfuscatorExecutor.java @@ -0,0 +1,108 @@ +package dev.imprex.orebfuscator.util.concurrent; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.LongAdder; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.statistics.OrebfuscatorStatistics; + +@NullMarked +public class OrebfuscatorExecutor implements Executor { + + private final ScheduledExecutorService scheduledExecutorService = Executors + .newSingleThreadScheduledExecutor(r -> new Thread(OrebfuscatorCore.THREAD_GROUP, r, "orebfuscator-scheduler")); + + private final int poolSize; + private final ExecutorService executorService; + + private final LongAdder run = new LongAdder(); + private long updateTime = System.nanoTime(); + + private final OrebfuscatorStatistics statistics; + + public OrebfuscatorExecutor(OrebfuscatorCore orebfuscator) { + this.statistics = orebfuscator.statistics(); + + this.poolSize = orebfuscator.config().advanced().threads(); + this.executorService = Executors.newFixedThreadPool(this.poolSize, OrebfuscatorThread::new); + + this.scheduledExecutorService.scheduleAtFixedRate(this::updateStatistics, 1L, 1L, TimeUnit.SECONDS); + } + + @Override + public void execute(Runnable command) { + Objects.requireNonNull(command); + + if (Thread.currentThread() instanceof OrebfuscatorThread) { + // don't time runnables if we're already on one of our threads as we can only enter + // our thread through a timed runnable + command.run(); + } else { + executorService.execute(new TimedRunnable(command)); + } + } + + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return scheduledExecutorService.schedule(command, delay, unit); + } + + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + return scheduledExecutorService.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + public boolean isShutdown() { + return this.scheduledExecutorService.isShutdown() || this.executorService.isShutdown(); + } + + public void shutdown() { + this.scheduledExecutorService.shutdownNow(); + this.executorService.shutdownNow(); + } + + private void updateStatistics() { + long time = System.nanoTime(); + try { + long run = this.run.sumThenReset(); + long available = this.poolSize * (time - this.updateTime); + + // invalid value don't know how to interpret it + if (run < 0 || run > available) { + return; + } + + double utilization = available == 0 ? 0.0 : (double) run / available; + statistics.obfuscation.executorUtilization.add(utilization); + } finally { + this.updateTime = time; + } + } + + private class TimedRunnable implements Runnable { + + private final long enqueuedAt = System.nanoTime(); + + private final Runnable delegate; + + public TimedRunnable(Runnable delegate) { + this.delegate = delegate; + } + + @Override + public void run() { + long start = System.nanoTime(); + statistics.obfuscation.executorWaitTime.add(start - this.enqueuedAt); + + try { + delegate.run(); + } finally { + run.add(System.nanoTime() - start); + } + } + } +} diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/concurrent/OrebfuscatorThread.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/concurrent/OrebfuscatorThread.java new file mode 100644 index 00000000..f334185b --- /dev/null +++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/concurrent/OrebfuscatorThread.java @@ -0,0 +1,32 @@ +package dev.imprex.orebfuscator.util.concurrent; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.random.RandomGenerator; +import java.util.random.RandomGenerator.SplittableGenerator; +import org.jspecify.annotations.NullMarked; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.logging.OfcLogger; + +@NullMarked +public class OrebfuscatorThread extends Thread implements UncaughtExceptionHandler { + + private static final SplittableGenerator ROOT_GENERATOR = SplittableGenerator.of("L64X128StarStarRandom"); + private static final AtomicInteger THREAD_ID = new AtomicInteger(); + + private final RandomGenerator randomGenerator = ROOT_GENERATOR.split(); + + public OrebfuscatorThread(Runnable target) { + super(OrebfuscatorCore.THREAD_GROUP, target, "orebfuscator-thread-" + THREAD_ID.getAndIncrement()); + this.setUncaughtExceptionHandler(this); + } + + public RandomGenerator random() { + return this.randomGenerator; + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + OfcLogger.error(String.format("Uncaught exception in: %s%n", thread.getName()), throwable); + } +} diff --git a/orebfuscator-core/src/main/resources/config/config-1.16.0.yml b/orebfuscator-core/src/main/resources/config/config-1.16.0.yml index 648d6ef6..0bdebb84 100644 --- a/orebfuscator-core/src/main/resources/config/config-1.16.0.yml +++ b/orebfuscator-core/src/main/resources/config/config-1.16.0.yml @@ -1,4 +1,4 @@ -version: 5 +version: 6 general: checkForUpdates: true updateOnBlockDamage: true @@ -7,12 +7,10 @@ general: updateRadius: 2 advanced: verbose: false + threads: -1 obfuscation: - threads: -1 timeout: 10000 - maxMillisecondsPerTick: 10 proximity: - threads: -1 defaultBucketSize: 50 threadCheckInterval: 50 playerCheckInterval: 5000 diff --git a/orebfuscator-core/src/main/resources/config/config-1.17.0.yml b/orebfuscator-core/src/main/resources/config/config-1.17.0.yml index 913da549..2626d3e8 100644 --- a/orebfuscator-core/src/main/resources/config/config-1.17.0.yml +++ b/orebfuscator-core/src/main/resources/config/config-1.17.0.yml @@ -1,4 +1,4 @@ -version: 5 +version: 6 general: checkForUpdates: true updateOnBlockDamage: true @@ -7,12 +7,10 @@ general: updateRadius: 2 advanced: verbose: false + threads: -1 obfuscation: - threads: -1 timeout: 10000 - maxMillisecondsPerTick: 10 proximity: - threads: -1 defaultBucketSize: 50 threadCheckInterval: 50 playerCheckInterval: 5000 diff --git a/orebfuscator-core/src/main/resources/config/config-1.18.0.yml b/orebfuscator-core/src/main/resources/config/config-1.18.0.yml index 221c5b5f..c006d5b6 100644 --- a/orebfuscator-core/src/main/resources/config/config-1.18.0.yml +++ b/orebfuscator-core/src/main/resources/config/config-1.18.0.yml @@ -1,4 +1,4 @@ -version: 5 +version: 6 general: checkForUpdates: true updateOnBlockDamage: true @@ -7,12 +7,10 @@ general: updateRadius: 2 advanced: verbose: false + threads: -1 obfuscation: - threads: -1 timeout: 10000 - maxMillisecondsPerTick: 10 proximity: - threads: -1 defaultBucketSize: 50 threadCheckInterval: 50 playerCheckInterval: 5000 diff --git a/orebfuscator-core/src/main/resources/config/config-1.20.3.yml b/orebfuscator-core/src/main/resources/config/config-1.20.3.yml index b00acd8a..7638d9a7 100644 --- a/orebfuscator-core/src/main/resources/config/config-1.20.3.yml +++ b/orebfuscator-core/src/main/resources/config/config-1.20.3.yml @@ -1,4 +1,4 @@ -version: 5 +version: 6 general: checkForUpdates: true updateOnBlockDamage: true @@ -7,12 +7,10 @@ general: updateRadius: 2 advanced: verbose: false + threads: -1 obfuscation: - threads: -1 timeout: 10000 - maxMillisecondsPerTick: 10 proximity: - threads: -1 defaultBucketSize: 50 threadCheckInterval: 50 playerCheckInterval: 5000 diff --git a/orebfuscator-core/src/main/resources/config/config-1.21.9.yml b/orebfuscator-core/src/main/resources/config/config-1.21.9.yml index de013be6..2460e868 100644 --- a/orebfuscator-core/src/main/resources/config/config-1.21.9.yml +++ b/orebfuscator-core/src/main/resources/config/config-1.21.9.yml @@ -1,4 +1,4 @@ -version: 5 +version: 6 general: checkForUpdates: true updateOnBlockDamage: true @@ -7,12 +7,10 @@ general: updateRadius: 2 advanced: verbose: false + threads: -1 obfuscation: - threads: -1 timeout: 10000 - maxMillisecondsPerTick: 10 proximity: - threads: -1 defaultBucketSize: 50 threadCheckInterval: 50 playerCheckInterval: 5000 diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/VersionTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/VersionTest.java new file mode 100644 index 00000000..a8018693 --- /dev/null +++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/VersionTest.java @@ -0,0 +1,131 @@ +package dev.imprex.orebfuscator.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class VersionTest { + + @Test + public void testParse() { + assertEquals(new Version(1, 0, 0, null), Version.parse("1")); + assertEquals(new Version(1, 2, 0, null), Version.parse("1.2")); + assertEquals(new Version(1, 2, 3, null), Version.parse("1.2.3")); + assertEquals(new Version(1, 2, 3, "-b0"), Version.parse("1.2.3-b0")); + + assertThrows(IllegalArgumentException.class, () -> Version.parse("a.1.c")); + assertThrows(IllegalArgumentException.class, () -> Version.parse("a.b.c")); + assertThrows(IllegalArgumentException.class, () -> Version.parse("fooBar")); + } + + @Test + public void testCompareTo() { + Version a = new Version(1, 0, 0, null); + Version b = new Version(1, 0, 0, null); + Version c = new Version(1, 2, 0, null); + Version d = new Version(1, 2, 3, null); + Version e = new Version(2, 0, 0, "-b0"); + Version f = new Version(2, 0, 0, "-b1"); + Version g = new Version(2, 0, 0, null); + + assertFalse(a.isAbove(b)); + assertTrue(a.isAtOrAbove(b)); + assertTrue(a.isAtOrBelow(b)); + assertFalse(a.isBelow(b)); + + assertFalse(a.isAbove(c)); + assertFalse(a.isAtOrAbove(c)); + assertTrue(a.isAtOrBelow(c)); + assertTrue(a.isBelow(c)); + + assertTrue(c.isAbove(a)); + assertTrue(c.isAtOrAbove(a)); + assertFalse(c.isAtOrBelow(a)); + assertFalse(c.isBelow(a)); + + assertFalse(c.isAbove(d)); + assertFalse(c.isAtOrAbove(d)); + assertTrue(c.isAtOrBelow(d)); + assertTrue(c.isBelow(d)); + + assertFalse(d.isAbove(e)); + assertFalse(d.isAtOrAbove(e)); + assertTrue(d.isAtOrBelow(e)); + assertTrue(d.isBelow(e)); + + assertFalse(e.isAbove(f)); + assertFalse(e.isAtOrAbove(f)); + assertTrue(e.isAtOrBelow(f)); + assertTrue(e.isBelow(f)); + + assertFalse(f.isAbove(g)); + assertFalse(f.isAtOrAbove(g)); + assertTrue(f.isAtOrBelow(g)); + assertTrue(f.isBelow(g)); + + List versions = new ArrayList<>(List.of(a, b, c, d, e, f, g)); + Collections.shuffle(versions); + + versions.sort(Comparator.naturalOrder()); + assertEquals(List.of(a, b, c, d, e, f, g), versions); + + versions.sort(Comparator.reverseOrder()); + assertEquals(List.of(g, f, e, d, c, b, a), versions); + } + + @Test + public void testHashCode() { + Version a = new Version(1, 2, 3, "-b0"); + Version b = new Version(1, 2, 3, "-b0"); + + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test + public void testEquals() { + Version a = new Version(1, 2, 3, "-b0"); + Version b = new Version(1, 2, 3, "-b0"); + Version c = new Version(1, 2, 3, "-b1"); + + assertEquals(a, a); + assertEquals(a, b); + + assertNotEquals(a, c); + assertNotEquals(b, c); + + assertNotEquals(a, new Object()); + } + + @Test + public void testToString() { + assertEquals("1.0.0", new Version(1, 0, 0, null).toString()); + assertEquals("1.2.0", new Version(1, 2, 0, null).toString()); + assertEquals("1.2.3", new Version(1, 2, 3, null).toString()); + assertEquals("1.2.3-b0", new Version(1, 2, 3, "-b0").toString()); + } + + @Test + public void testJson() { + DummyClass dummy = new DummyClass( + new Version(1, 0, 0, null), + new Version(2, 3, 4, "-alpha")); + + JsonElement json = AbstractHttpService.GSON.toJsonTree(dummy); + DummyClass other = AbstractHttpService.GSON.fromJson(json, DummyClass.class); + + assertEquals(dummy, other); + } + + private record DummyClass(Version fieldA, Version fieldB) { + } +} diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/WeightedRandomTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/WeightedRandomTest.java index 7dd6e5d0..1396e2c9 100644 --- a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/WeightedRandomTest.java +++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/WeightedRandomTest.java @@ -14,7 +14,7 @@ public class WeightedRandomTest { private static final long SEED = 1337; - private static final int SAMPLES = 250_000; + private static final int SAMPLES = 1_000_000; private static final double SIGMA = 2.0; private WeightedRandom createWeightedRandom(Map weights) { diff --git a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorNms.java b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorNms.java index 89fd71d1..95422e69 100644 --- a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorNms.java +++ b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/OrebfuscatorNms.java @@ -1,17 +1,19 @@ package net.imprex.orebfuscator; -import java.lang.reflect.Constructor; -import org.bukkit.World; -import org.bukkit.entity.Player; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.interop.RegistryAccessor; import dev.imprex.orebfuscator.logging.OfcLogger; import dev.imprex.orebfuscator.util.BlockPos; +import java.lang.reflect.Constructor; +import java.util.concurrent.CompletableFuture; import net.imprex.orebfuscator.nms.NmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.imprex.orebfuscator.util.MinecraftVersion; import net.imprex.orebfuscator.util.ServerVersion; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.Nullable; public class OrebfuscatorNms { @@ -51,36 +53,12 @@ public static AbstractRegionFileCache createRegionFileCache(Config config) { return instance.createRegionFileCache(config); } - public static int getUniqueBlockStateCount() { - return instance.getUniqueBlockStateCount(); - } - - public static int getMaxBitsPerBlockState() { - return instance.getMaxBitsPerBlockState(); - } - - public static boolean isAir(int blockId) { - return instance.isAir(blockId); - } - - public static boolean isOccluding(int blockId) { - return instance.isOccluding(blockId); - } - - public static boolean isBlockEntity(int blockId) { - return instance.isBlockEntity(blockId); - } - - public static ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - return instance.getReadOnlyChunk(world, chunkX, chunkZ); - } - - public static int getBlockState(World world, BlockPos position) { - return getBlockState(world, position.x(), position.y(), position.z()); + public static CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return instance.getChunkFuture(world, chunkX, chunkZ); } - public static int getBlockState(World world, int x, int y, int z) { - return instance.getBlockState(world, x, y, z); + public static @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { + return instance.getChunkNow(world, chunkX, chunkZ); } public static void sendBlockUpdates(World world, Iterable iterable) { diff --git a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/AbstractNmsManager.java b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/AbstractNmsManager.java index a1b3e411..59f36ea7 100644 --- a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/AbstractNmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/AbstractNmsManager.java @@ -1,23 +1,24 @@ package net.imprex.orebfuscator.nms; -import java.util.HashMap; -import java.util.Map; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import dev.imprex.orebfuscator.reflect.Reflector; import dev.imprex.orebfuscator.reflect.accessor.MethodAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; -import dev.imprex.orebfuscator.util.MathUtil; import dev.imprex.orebfuscator.util.NamespacedKey; +import dev.imprex.orebfuscator.util.QuickMaths; +import java.util.HashMap; +import java.util.Map; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public abstract class AbstractNmsManager implements NmsManager { - private static MethodAccessor worldGetHandle; - private static MethodAccessor playerGetHandle; + private static @Nullable MethodAccessor worldGetHandle; + private static @Nullable MethodAccessor playerGetHandle; protected static T worldHandle(World world, Class targetClass) { if (worldGetHandle == null) { @@ -52,7 +53,7 @@ protected static T playerHandle(Player player, Class targetClass) { public AbstractNmsManager(int uniqueBlockStateCount) { this.uniqueBlockStateCount = uniqueBlockStateCount; - this.maxBitsPerBlockState = MathUtil.ceilLog2(uniqueBlockStateCount); + this.maxBitsPerBlockState = QuickMaths.ceilLog2(uniqueBlockStateCount); this.blockStates = new BlockStateProperties[uniqueBlockStateCount]; } @@ -80,13 +81,13 @@ public final int getMaxBitsPerBlockState() { } @Override - public final @Nullable BlockProperties getBlockByName(@NotNull String name) { - return this.blocks.get(NamespacedKey.fromString(name)); + public final @Nullable BlockProperties getBlockByName(String name) { + return NamespacedKey.tryParse(name).map(this.blocks::get).orElse(null); } @Override - public final @Nullable BlockTag getBlockTagByName(@NotNull String name) { - return this.tags.get(NamespacedKey.fromString(name)); + public final @Nullable BlockTag getBlockTagByName(String name) { + return NamespacedKey.tryParse(name).map(this.tags::get).orElse(null); } @Override @@ -94,6 +95,11 @@ public final boolean isAir(int id) { return this.blockStates[id].isAir(); } + @Override + public final boolean isLava(int id) { + return this.blockStates[id].isLava(); + } + @Override public final boolean isOccluding(int id) { return this.blockStates[id].isOccluding(); diff --git a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/NmsManager.java index b61e354d..b1a98af7 100644 --- a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/NmsManager.java @@ -1,20 +1,24 @@ package net.imprex.orebfuscator.nms; -import org.bukkit.World; -import org.bukkit.entity.Player; - import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.interop.RegistryAccessor; import dev.imprex.orebfuscator.util.BlockPos; +import java.util.concurrent.CompletableFuture; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public interface NmsManager extends RegistryAccessor { AbstractRegionFileCache createRegionFileCache(Config config); - ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ); + CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ); - int getBlockState(World world, int x, int y, int z); + @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ); void sendBlockUpdates(World world, Iterable iterable); diff --git a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/ReadOnlyChunk.java b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/ReadOnlyChunk.java deleted file mode 100644 index ad28dbe3..00000000 --- a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/nms/ReadOnlyChunk.java +++ /dev/null @@ -1,6 +0,0 @@ -package net.imprex.orebfuscator.nms; - -public interface ReadOnlyChunk { - - int getBlockState(int x, int y, int z); -} diff --git a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/util/MinecraftVersion.java b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/util/MinecraftVersion.java index 25dcbf1e..c1151084 100644 --- a/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/util/MinecraftVersion.java +++ b/orebfuscator-nms/orebfuscator-nms-api/src/main/java/net/imprex/orebfuscator/util/MinecraftVersion.java @@ -1,18 +1,16 @@ package net.imprex.orebfuscator.util; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.util.Version; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.bukkit.Bukkit; -import dev.imprex.orebfuscator.logging.OfcLogger; -import dev.imprex.orebfuscator.util.Version; - public final class MinecraftVersion { - private static final class NmsMapping { + private record NmsMapping(Version version, String nmsVersion) { private static final List MAPPINGS = new ArrayList<>(); @@ -42,19 +40,15 @@ public static String get(Version version) { throw new RuntimeException("Can't get nms package version for minecraft version: " + version); } - private final Version version; - private final String nmsVersion; - - public NmsMapping(String version, String nmsVersion) { - this.version = Version.parse(version); - this.nmsVersion = nmsVersion; + private NmsMapping(String version, String nmsVersion) { + this(Version.parse(version), nmsVersion); } } private static final Pattern PACKAGE_PATTERN = Pattern.compile("org\\.bukkit\\.craftbukkit\\.(v\\d+_\\d+_R\\d+)"); private static final Version CURRENT_VERSION = Version.parse(Bukkit.getBukkitVersion()); - private static String NMS_VERSION; + private static final String NMS_VERSION; static { String craftBukkitPackage = Bukkit.getServer().getClass().getPackage().getName(); diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/pom.xml b/orebfuscator-nms/orebfuscator-nms-v1_16_R1/pom.xml deleted file mode 100644 index adf33b2a..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/pom.xml +++ /dev/null @@ -1,35 +0,0 @@ - - 4.0.0 - - - net.imprex - orebfuscator-nms - ${revision} - - - orebfuscator-nms-v1_16_R1 - jar - - - - net.imprex - orebfuscator-nms-api - ${revision} - provided - - - net.dmulloy2 - ProtocolLib - ${dependency.protocollib.version} - provided - - - org.spigotmc - spigot - 1.16.1-R0.1-SNAPSHOT - provided - - - \ No newline at end of file diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/NmsManager.java deleted file mode 100644 index 3e97717b..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/NmsManager.java +++ /dev/null @@ -1,205 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_16_R1; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import org.bukkit.World; -import org.bukkit.entity.Player; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.ChunkCoordIntPair; -import com.comphenix.protocol.wrappers.MultiBlockChangeInfo; -import com.comphenix.protocol.wrappers.WrappedBlockData; -import com.google.common.collect.ImmutableList; -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.Config; -import dev.imprex.orebfuscator.util.BlockPos; -import dev.imprex.orebfuscator.util.BlockProperties; -import dev.imprex.orebfuscator.util.BlockStateProperties; -import dev.imprex.orebfuscator.util.BlockTag; -import dev.imprex.orebfuscator.util.NamespacedKey; -import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.server.v1_16_R1.Block; -import net.minecraft.server.v1_16_R1.BlockAccessAir; -import net.minecraft.server.v1_16_R1.BlockPosition; -import net.minecraft.server.v1_16_R1.Blocks; -import net.minecraft.server.v1_16_R1.Chunk; -import net.minecraft.server.v1_16_R1.ChunkProviderServer; -import net.minecraft.server.v1_16_R1.ChunkSection; -import net.minecraft.server.v1_16_R1.EntityPlayer; -import net.minecraft.server.v1_16_R1.IBlockData; -import net.minecraft.server.v1_16_R1.IRegistry; -import net.minecraft.server.v1_16_R1.MinecraftKey; -import net.minecraft.server.v1_16_R1.Packet; -import net.minecraft.server.v1_16_R1.PacketListenerPlayOut; -import net.minecraft.server.v1_16_R1.ResourceKey; -import net.minecraft.server.v1_16_R1.Tag; -import net.minecraft.server.v1_16_R1.TagsBlock; -import net.minecraft.server.v1_16_R1.TileEntity; -import net.minecraft.server.v1_16_R1.WorldServer; - -public class NmsManager extends AbstractNmsManager { - - private static final int BLOCK_ID_AIR = Block.getCombinedId(Blocks.AIR.getBlockData()); - - static int getBlockState(Chunk chunk, int x, int y, int z) { - ChunkSection[] sections = chunk.getSections(); - - int sectionIndex = y >> 4; - if (sectionIndex >= 0 && sectionIndex < sections.length) { - ChunkSection section = sections[sectionIndex]; - if (section != null && !ChunkSection.a(section)) { - return Block.getCombinedId(section.getType(x & 0xF, y & 0xF, z & 0xF)); - } - } - - return BLOCK_ID_AIR; - } - - private static WorldServer level(World world) { - return worldHandle(world, WorldServer.class); - } - - private static EntityPlayer player(Player player) { - return playerHandle(player, EntityPlayer.class); - } - - public NmsManager() { - super(Block.REGISTRY_ID.a()); - - for (Map.Entry, Block> entry : IRegistry.BLOCK.c()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().a().toString()); - Block block = entry.getValue(); - - ImmutableList possibleBlockStates = block.getStates().a(); - BlockProperties.Builder builder = BlockProperties.builder(namespacedKey); - - for (IBlockData blockState : possibleBlockStates) { - BlockStateProperties properties = BlockStateProperties.builder(Block.getCombinedId(blockState)) - .withIsAir(blockState.isAir()) - .withIsOccluding(blockState.i(BlockAccessAir.INSTANCE, BlockPosition.ZERO)/*isSolidRender*/) - .withIsBlockEntity(block.isTileEntity()) - .withIsDefaultState(Objects.equals(block.getBlockData(), blockState)) - .build(); - - builder.withBlockState(properties); - } - - registerBlockProperties(builder.build()); - } - - for (Entry> entry : TagsBlock.b().b().entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().toString()); - - Set blocks = new HashSet<>(); - for (Block block : entry.getValue().getTagged()) { - BlockProperties properties = getBlockByName(IRegistry.BLOCK.getKey(block).toString()); - if (properties != null) { - blocks.add(properties); - } - } - - registerBlockTag(new BlockTag(namespacedKey, blocks)); - } - } - - @Override - public AbstractRegionFileCache createRegionFileCache(Config config) { - return new RegionFileCache(config.cache()); - } - - @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ChunkProviderServer chunkProviderServer = level(world).getChunkProvider(); - Chunk chunk = chunkProviderServer.getChunkAt(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); - } - - @Override - public int getBlockState(World world, int x, int y, int z) { - ChunkProviderServer serverChunkCache = level(world).getChunkProvider(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - - Chunk chunk = serverChunkCache.getChunkAt(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; - } - - return getBlockState(chunk, x, y, z); - } - - @Override - public void sendBlockUpdates(World world, Iterable iterable) { - ChunkProviderServer serverChunkCache = level(world).getChunkProvider(); - BlockPosition.MutableBlockPosition position = new BlockPosition.MutableBlockPosition(); - - for (dev.imprex.orebfuscator.util.BlockPos pos : iterable) { - position.c(pos.x(), pos.y(), pos.z()); - serverChunkCache.flagDirty(position); - } - } - - @Override - public void sendBlockUpdates(Player player, Iterable iterable) { - EntityPlayer serverPlayer = player(player); - WorldServer level = serverPlayer.getWorldServer(); - ChunkProviderServer serverChunkCache = level.getChunkProvider(); - - BlockPosition.MutableBlockPosition position = new BlockPosition.MutableBlockPosition(); - Map> sectionPackets = new HashMap<>(); - List> blockEntityPackets = new ArrayList<>(); - - for (dev.imprex.orebfuscator.util.BlockPos pos : iterable) { - if (!serverChunkCache.isChunkLoaded(pos.x() >> 4, pos.z() >> 4)) { - continue; - } - - position.c(pos.x(), pos.y(), pos.z()); - IBlockData blockState = level.getType(position); - - ChunkCoordIntPair chunkCoord = new ChunkCoordIntPair(pos.x() >> 4, pos.z() >> 4); - short location = (short) ((pos.x() & 0xF) << 12 | (pos.z() & 0xF) << 8 | pos.y()); - - sectionPackets.computeIfAbsent(chunkCoord, key -> new ArrayList<>()) - .add(new MultiBlockChangeInfo(location, WrappedBlockData.fromHandle(blockState), chunkCoord)); - - if (blockState.getBlock().isTileEntity()) { - TileEntity blockEntity = level.getTileEntity(position); - if (blockEntity != null) { - blockEntityPackets.add(blockEntity.getUpdatePacket()); - } - } - } - - for (Map.Entry> entry : sectionPackets.entrySet()) { - List blockStates = entry.getValue(); - if (blockStates.size() == 1) { - MultiBlockChangeInfo blockEntry = blockStates.get(0); - var blockPosition = new com.comphenix.protocol.wrappers.BlockPosition( - blockEntry.getAbsoluteX(), blockEntry.getY(), blockEntry.getAbsoluteZ()); - - PacketContainer packet = new PacketContainer(PacketType.Play.Server.BLOCK_CHANGE); - packet.getBlockPositionModifier().write(0, blockPosition); - packet.getBlockData().write(0, blockEntry.getData()); - serverPlayer.playerConnection.sendPacket((Packet) packet.getHandle()); - } else { - PacketContainer packet = new PacketContainer(PacketType.Play.Server.MULTI_BLOCK_CHANGE); - packet.getChunkCoordIntPairs().write(0, entry.getKey()); - packet.getMultiBlockChangeInfoArrays().write(0, blockStates.toArray(MultiBlockChangeInfo[]::new)); - serverPlayer.playerConnection.sendPacket((Packet) packet.getHandle()); - } - } - - for (Packet packet : blockEntityPackets) { - serverPlayer.playerConnection.sendPacket(packet); - } - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/ReadOnlyChunkWrapper.java deleted file mode 100644 index ff4a535f..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_16_R1; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.server.v1_16_R1.Chunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final Chunk chunk; - - ReadOnlyChunkWrapper(Chunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/RegionFileCache.java deleted file mode 100644 index b9b1c1c3..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R1/src/main/java/net/imprex/orebfuscator/nms/v1_16_R1/RegionFileCache.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_16_R1; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.minecraft.server.v1_16_R1.ChunkCoordIntPair; -import net.minecraft.server.v1_16_R1.DedicatedServer; -import net.minecraft.server.v1_16_R1.RegionFile; -import net.minecraft.server.v1_16_R1.RegionFileCompression; - -public class RegionFileCache extends AbstractRegionFileCache { - - RegionFileCache(CacheConfig cacheConfig) { - super(cacheConfig); - } - - @Override - protected RegionFile createRegionFile(Path path) throws IOException { - boolean isSyncChunkWrites = serverHandle(Bukkit.getServer(), DedicatedServer.class).isSyncChunkWrites(); - return new RegionFile(path, path.getParent(), RegionFileCompression.c, isSyncChunkWrites); - } - - @Override - protected void closeRegionFile(RegionFile t) throws IOException { - t.close(); - } - - @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { - return t.a(new ChunkCoordIntPair(key.x(), key.z())); - } - - @Override - protected DataOutputStream createOutputStream(RegionFile t, ChunkCacheKey key) throws IOException { - return t.c(new ChunkCoordIntPair(key.x(), key.z())); - } -} \ No newline at end of file diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/pom.xml b/orebfuscator-nms/orebfuscator-nms-v1_16_R2/pom.xml deleted file mode 100644 index 7765f476..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/pom.xml +++ /dev/null @@ -1,35 +0,0 @@ - - 4.0.0 - - - net.imprex - orebfuscator-nms - ${revision} - - - orebfuscator-nms-v1_16_R2 - jar - - - - net.imprex - orebfuscator-nms-api - ${revision} - provided - - - net.dmulloy2 - ProtocolLib - ${dependency.protocollib.version} - provided - - - org.spigotmc - spigot - 1.16.3-R0.1-SNAPSHOT - provided - - - \ No newline at end of file diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/NmsManager.java deleted file mode 100644 index 497cd2f1..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/NmsManager.java +++ /dev/null @@ -1,209 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_16_R2; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import org.bukkit.World; -import org.bukkit.entity.Player; -import com.comphenix.protocol.events.PacketContainer; -import com.google.common.collect.ImmutableList; -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.Config; -import dev.imprex.orebfuscator.util.BlockPos; -import dev.imprex.orebfuscator.util.BlockProperties; -import dev.imprex.orebfuscator.util.BlockStateProperties; -import dev.imprex.orebfuscator.util.BlockTag; -import dev.imprex.orebfuscator.util.NamespacedKey; -import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.server.v1_16_R2.Block; -import net.minecraft.server.v1_16_R2.BlockAccessAir; -import net.minecraft.server.v1_16_R2.BlockPosition; -import net.minecraft.server.v1_16_R2.Blocks; -import net.minecraft.server.v1_16_R2.Chunk; -import net.minecraft.server.v1_16_R2.ChunkProviderServer; -import net.minecraft.server.v1_16_R2.ChunkSection; -import net.minecraft.server.v1_16_R2.EntityPlayer; -import net.minecraft.server.v1_16_R2.IBlockData; -import net.minecraft.server.v1_16_R2.IRegistry; -import net.minecraft.server.v1_16_R2.MinecraftKey; -import net.minecraft.server.v1_16_R2.Packet; -import net.minecraft.server.v1_16_R2.PacketListenerPlayOut; -import net.minecraft.server.v1_16_R2.PacketPlayOutBlockChange; -import net.minecraft.server.v1_16_R2.PacketPlayOutMultiBlockChange; -import net.minecraft.server.v1_16_R2.ResourceKey; -import net.minecraft.server.v1_16_R2.SectionPosition; -import net.minecraft.server.v1_16_R2.Tag; -import net.minecraft.server.v1_16_R2.TagsBlock; -import net.minecraft.server.v1_16_R2.TileEntity; -import net.minecraft.server.v1_16_R2.WorldServer; - -public class NmsManager extends AbstractNmsManager { - - private static final int BLOCK_ID_AIR = Block.getCombinedId(Blocks.AIR.getBlockData()); - - static int getBlockState(Chunk chunk, int x, int y, int z) { - ChunkSection[] sections = chunk.getSections(); - - int sectionIndex = y >> 4; - if (sectionIndex >= 0 && sectionIndex < sections.length) { - ChunkSection section = sections[sectionIndex]; - if (section != null && !ChunkSection.a(section)) { - return Block.getCombinedId(section.getType(x & 0xF, y & 0xF, z & 0xF)); - } - } - - return BLOCK_ID_AIR; - } - - private static WorldServer level(World world) { - return worldHandle(world, WorldServer.class); - } - - private static EntityPlayer player(Player player) { - return playerHandle(player, EntityPlayer.class); - } - - public NmsManager() { - super(Block.REGISTRY_ID.a()); - - for (Map.Entry, Block> entry : IRegistry.BLOCK.d()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().a().toString()); - Block block = entry.getValue(); - - ImmutableList possibleBlockStates = block.getStates().a(); - BlockProperties.Builder builder = BlockProperties.builder(namespacedKey); - - for (IBlockData blockState : possibleBlockStates) { - BlockStateProperties properties = BlockStateProperties.builder(Block.getCombinedId(blockState)) - .withIsAir(blockState.isAir()) - .withIsOccluding(blockState.i(BlockAccessAir.INSTANCE, BlockPosition.ZERO)/*isSolidRender*/) - .withIsBlockEntity(block.isTileEntity()) - .withIsDefaultState(Objects.equals(block.getBlockData(), blockState)) - .build(); - - builder.withBlockState(properties); - } - - registerBlockProperties(builder.build()); - } - - for (Entry> entry : TagsBlock.a().a().entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().toString()); - - Set blocks = new HashSet<>(); - for (Block block : entry.getValue().getTagged()) { - BlockProperties properties = getBlockByName(IRegistry.BLOCK.getKey(block).toString()); - if (properties != null) { - blocks.add(properties); - } - } - - registerBlockTag(new BlockTag(namespacedKey, blocks)); - } - } - - @Override - public AbstractRegionFileCache createRegionFileCache(Config config) { - return new RegionFileCache(config.cache()); - } - - @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ChunkProviderServer chunkProviderServer = level(world).getChunkProvider(); - Chunk chunk = chunkProviderServer.getChunkAt(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); - } - - @Override - public int getBlockState(World world, int x, int y, int z) { - ChunkProviderServer serverChunkCache = level(world).getChunkProvider(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - - Chunk chunk = serverChunkCache.getChunkAt(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; - } - - return getBlockState(chunk, x, y, z); - } - - @Override - public void sendBlockUpdates(World world, Iterable iterable) { - ChunkProviderServer serverChunkCache = level(world).getChunkProvider(); - BlockPosition.MutableBlockPosition position = new BlockPosition.MutableBlockPosition(); - - for (dev.imprex.orebfuscator.util.BlockPos pos : iterable) { - position.c(pos.x(), pos.y(), pos.z()); - serverChunkCache.flagDirty(position); - } - } - - @Override - public void sendBlockUpdates(Player player, Iterable iterable) { - EntityPlayer serverPlayer = player(player); - WorldServer level = serverPlayer.getWorldServer(); - ChunkProviderServer serverChunkCache = level.getChunkProvider(); - - BlockPosition.MutableBlockPosition position = new BlockPosition.MutableBlockPosition(); - Map> sectionPackets = new HashMap<>(); - List> blockEntityPackets = new ArrayList<>(); - - for (dev.imprex.orebfuscator.util.BlockPos pos : iterable) { - if (!serverChunkCache.isChunkLoaded(pos.x() >> 4, pos.z() >> 4)) { - continue; - } - - position.c(pos.x(), pos.y(), pos.z()); - IBlockData blockState = level.getType(position); - - sectionPackets.computeIfAbsent(SectionPosition.a(position), key -> new HashMap<>()) - .put(SectionPosition.b(position), blockState); - - if (blockState.getBlock().isTileEntity()) { - TileEntity blockEntity = level.getTileEntity(position); - if (blockEntity != null) { - blockEntityPackets.add(blockEntity.getUpdatePacket()); - } - } - } - - for (Map.Entry> entry : sectionPackets.entrySet()) { - Map blockStates = entry.getValue(); - if (blockStates.size() == 1) { - Map.Entry blockEntry = blockStates.entrySet().iterator().next(); - BlockPosition blockPosition = entry.getKey().g(blockEntry.getKey()); - serverPlayer.playerConnection.sendPacket(new PacketPlayOutBlockChange(blockPosition, blockEntry.getValue())); - } else { - // fix #324: use empty constructor cause ChunkSection can only be null for spigot forks - PacketContainer packet = PacketContainer.fromPacket(new PacketPlayOutMultiBlockChange()); - packet.getSpecificModifier(SectionPosition.class).write(0, entry.getKey()); - packet.getSpecificModifier(short[].class).write(0, toShortArray(blockStates.keySet())); - packet.getSpecificModifier(IBlockData[].class).write(0, blockStates.values().toArray(IBlockData[]::new)); - serverPlayer.playerConnection.sendPacket((Packet) packet.getHandle()); - } - } - - for (Packet packet : blockEntityPackets) { - serverPlayer.playerConnection.sendPacket(packet); - } - } - - private static short[] toShortArray(Set set) { - short[] array = new short[set.size()]; - - int i = 0; - for (Short value : set) { - array[i++] = value; - } - - return array; - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/ReadOnlyChunkWrapper.java deleted file mode 100644 index ce66968f..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_16_R2; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.server.v1_16_R2.Chunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final Chunk chunk; - - ReadOnlyChunkWrapper(Chunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/RegionFileCache.java deleted file mode 100644 index 72800adf..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R2/src/main/java/net/imprex/orebfuscator/nms/v1_16_R2/RegionFileCache.java +++ /dev/null @@ -1,44 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_16_R2; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.minecraft.server.v1_16_R2.ChunkCoordIntPair; -import net.minecraft.server.v1_16_R2.DedicatedServer; -import net.minecraft.server.v1_16_R2.RegionFile; -import net.minecraft.server.v1_16_R2.RegionFileCompression; - -public class RegionFileCache extends AbstractRegionFileCache { - - RegionFileCache(CacheConfig cacheConfig) { - super(cacheConfig); - } - - @Override - protected RegionFile createRegionFile(Path path) throws IOException { - boolean isSyncChunkWrites = serverHandle(Bukkit.getServer(), DedicatedServer.class).isSyncChunkWrites(); - return new RegionFile(path, path.getParent(), RegionFileCompression.c, isSyncChunkWrites); - } - - @Override - protected void closeRegionFile(RegionFile t) throws IOException { - t.close(); - } - - @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { - return t.a(new ChunkCoordIntPair(key.x(), key.z())); - } - - @Override - protected DataOutputStream createOutputStream(RegionFile t, ChunkCacheKey key) throws IOException { - return t.c(new ChunkCoordIntPair(key.x(), key.z())); - } -} \ No newline at end of file diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/DefaultChunkAccessor.java similarity index 50% rename from orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/ReadOnlyChunkWrapper.java rename to orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/DefaultChunkAccessor.java index 1fb7a511..caffc3eb 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/ReadOnlyChunkWrapper.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/DefaultChunkAccessor.java @@ -1,15 +1,9 @@ package net.imprex.orebfuscator.nms.v1_16_R3; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import net.minecraft.server.v1_16_R3.Chunk; -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final Chunk chunk; - - ReadOnlyChunkWrapper(Chunk chunk) { - this.chunk = chunk; - } +public record DefaultChunkAccessor(Chunk chunk) implements ChunkAccessor { @Override public int getBlockState(int x, int y, int z) { diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/NmsManager.java index 3fd5f17f..8269e67d 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/NmsManager.java @@ -1,26 +1,25 @@ package net.imprex.orebfuscator.nms.v1_16_R3; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketContainer; import com.google.common.collect.ImmutableList; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockPos; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.server.v1_16_R3.Block; import net.minecraft.server.v1_16_R3.BlockAccessAir; import net.minecraft.server.v1_16_R3.BlockPosition; @@ -42,13 +41,18 @@ import net.minecraft.server.v1_16_R3.TagsBlock; import net.minecraft.server.v1_16_R3.TileEntity; import net.minecraft.server.v1_16_R3.WorldServer; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getCombinedId(Blocks.AIR.getBlockData()); static int getBlockState(Chunk chunk, int x, int y, int z) { - ChunkSection[] sections = chunk.getSections(); + @Nullable ChunkSection[] sections = chunk.getSections(); int sectionIndex = y >> 4; if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -72,8 +76,8 @@ private static EntityPlayer player(Player player) { public NmsManager() { super(Block.REGISTRY_ID.a()); - for (Map.Entry, Block> entry : IRegistry.BLOCK.d()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().a().toString()); + for (Entry, Block> entry : IRegistry.BLOCK.d()) { + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().a().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStates().a(); @@ -82,6 +86,7 @@ public NmsManager() { for (IBlockData blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getCombinedId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.i(BlockAccessAir.INSTANCE, BlockPosition.ZERO)/*isSolidRender*/) .withIsBlockEntity(block.isTileEntity()) .withIsDefaultState(Objects.equals(block.getBlockData(), blockState)) @@ -94,7 +99,7 @@ public NmsManager() { } for (Entry> entry : TagsBlock.a().a().entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().toString()); Set blocks = new HashSet<>(); for (Block block : entry.getValue().getTagged()) { @@ -114,33 +119,29 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ChunkProviderServer chunkProviderServer = level(world).getChunkProvider(); - Chunk chunk = chunkProviderServer.getChunkAt(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + Chunk chunk = level(world).getChunkProvider().getChunkAt(chunkX, chunkZ, true); + return CompletableFuture.completedFuture(chunk != null ? new DefaultChunkAccessor(chunk) : null); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ChunkProviderServer serverChunkCache = level(world).getChunkProvider(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - Chunk chunk = serverChunkCache.getChunkAt(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + Chunk chunk = serverChunkCache.a(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunkAt(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override - public void sendBlockUpdates(World world, Iterable iterable) { + public void sendBlockUpdates(World world, Iterable iterable) { ChunkProviderServer serverChunkCache = level(world).getChunkProvider(); BlockPosition.MutableBlockPosition position = new BlockPosition.MutableBlockPosition(); - for (dev.imprex.orebfuscator.util.BlockPos pos : iterable) { + for (BlockPos pos : iterable) { position.c(pos.x(), pos.y(), pos.z()); serverChunkCache.flagDirty(position); } @@ -156,7 +157,7 @@ public void sendBlockUpdates(Player player, Iterable iterable) { Map> sectionPackets = new HashMap<>(); List> blockEntityPackets = new ArrayList<>(); - for (dev.imprex.orebfuscator.util.BlockPos pos : iterable) { + for (BlockPos pos : iterable) { if (!serverChunkCache.isChunkLoaded(pos.x() >> 4, pos.z() >> 4)) { continue; } @@ -170,15 +171,18 @@ public void sendBlockUpdates(Player player, Iterable iterable) { if (blockState.getBlock().isTileEntity()) { TileEntity blockEntity = level.getTileEntity(position); if (blockEntity != null) { - blockEntityPackets.add(blockEntity.getUpdatePacket()); + Packet packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } - for (Map.Entry> entry : sectionPackets.entrySet()) { + for (Entry> entry : sectionPackets.entrySet()) { Map blockStates = entry.getValue(); if (blockStates.size() == 1) { - Map.Entry blockEntry = blockStates.entrySet().iterator().next(); + Entry blockEntry = blockStates.entrySet().iterator().next(); BlockPosition blockPosition = entry.getKey().g(blockEntry.getKey()); serverPlayer.playerConnection.sendPacket(new PacketPlayOutBlockChange(blockPosition, blockEntry.getValue())); } else { diff --git a/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/RegionFileCache.java index 2791a238..4a081cf9 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_16_R3/src/main/java/net/imprex/orebfuscator/nms/v1_16_R3/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_16_R3; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.v1_16_R3.ChunkCoordIntPair; import net.minecraft.server.v1_16_R3.DedicatedServer; import net.minecraft.server.v1_16_R3.RegionFile; import net.minecraft.server.v1_16_R3.RegionFileCompression; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.a(new ChunkCoordIntPair(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/DefaultChunkAccessor.java new file mode 100644 index 00000000..8ed70d1d --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_17_R1; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/NmsManager.java index 22fb66ee..d80997f8 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/NmsManager.java @@ -1,25 +1,24 @@ package net.imprex.orebfuscator.nms.v1_17_R1; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; import com.google.common.collect.ImmutableList; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : Registry.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } for (Map.Entry> entry : BlockTags.getAllTags().getAllTags().entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().toString()); Set blocks = new HashSet<>(); for (Block block : entry.getValue().getValues()) { @@ -112,25 +119,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkProvider(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkProvider() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkProvider(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -169,7 +176,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/ReadOnlyChunkWrapper.java deleted file mode 100644 index 290da572..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_17_R1; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/RegionFileCache.java index 7e2724c9..e084291e 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_17_R1/src/main/java/net/imprex/orebfuscator/nms/v1_17_R1/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_17_R1; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/DefaultChunkAccessor.java new file mode 100644 index 00000000..c5866dd2 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_18_R1; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/NmsManager.java index a37c2c4f..1008fe47 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/NmsManager.java @@ -1,26 +1,25 @@ package net.imprex.orebfuscator.nms.v1_18_R1; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketContainer; import com.google.common.collect.ImmutableList; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -40,15 +39,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -73,7 +79,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : Registry.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -82,6 +88,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -94,7 +101,7 @@ public NmsManager() { } for (Map.Entry> entry : BlockTags.getAllTags().getAllTags().entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().toString()); Set blocks = new HashSet<>(); for (Block block : entry.getValue().getValues()) { @@ -114,25 +121,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -170,7 +177,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/ReadOnlyChunkWrapper.java deleted file mode 100644 index 67796e6c..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_18_R1; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/RegionFileCache.java index a6a4b47a..d7b94a55 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_18_R1/src/main/java/net/imprex/orebfuscator/nms/v1_18_R1/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_18_R1; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/DefaultChunkAccessor.java new file mode 100644 index 00000000..f3d48857 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_18_R2; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/NmsManager.java index fb33df14..e25fdd72 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/NmsManager.java @@ -1,28 +1,27 @@ package net.imprex.orebfuscator.nms.v1_18_R2; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketContainer; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -39,15 +38,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -72,7 +78,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : Registry.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -81,6 +87,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -93,7 +100,7 @@ public NmsManager() { } Registry.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -111,25 +118,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -167,7 +174,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/ReadOnlyChunkWrapper.java deleted file mode 100644 index 6fd0ed7f..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_18_R2; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/RegionFileCache.java index 4780508b..94585d2b 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_18_R2/src/main/java/net/imprex/orebfuscator/nms/v1_18_R2/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_18_R2; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/DefaultChunkAccessor.java new file mode 100644 index 00000000..a05db925 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_19_R1; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/NmsManager.java index c662f8c3..686de3b0 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/NmsManager.java @@ -1,27 +1,26 @@ package net.imprex.orebfuscator.nms.v1_19_R1; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : Registry.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } Registry.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -110,25 +117,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -166,7 +173,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/ReadOnlyChunkWrapper.java deleted file mode 100644 index 669bdedf..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_19_R1; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/RegionFileCache.java index 89beb5f2..68ee8543 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R1/src/main/java/net/imprex/orebfuscator/nms/v1_19_R1/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_19_R1; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/DefaultChunkAccessor.java new file mode 100644 index 00000000..7c9339d4 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_19_R2; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/NmsManager.java index 7b808b80..87766e48 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/NmsManager.java @@ -1,27 +1,26 @@ package net.imprex.orebfuscator.nms.v1_19_R2; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -110,25 +117,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -166,7 +173,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/ReadOnlyChunkWrapper.java deleted file mode 100644 index 88f7fe04..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_19_R2; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/RegionFileCache.java index 202c568a..96ae9c94 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R2/src/main/java/net/imprex/orebfuscator/nms/v1_19_R2/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_19_R2; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/DefaultChunkAccessor.java new file mode 100644 index 00000000..bd2462f3 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_19_R3; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/NmsManager.java index 275f4e2c..f660b6d4 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/NmsManager.java @@ -1,27 +1,26 @@ package net.imprex.orebfuscator.nms.v1_19_R3; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -110,25 +117,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -166,7 +173,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/ReadOnlyChunkWrapper.java deleted file mode 100644 index 129b5aa0..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_19_R3; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/RegionFileCache.java index 43f958d5..e853aa94 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_19_R3/src/main/java/net/imprex/orebfuscator/nms/v1_19_R3/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_19_R3; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/DefaultChunkAccessor.java new file mode 100644 index 00000000..949eec8e --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_20_R1; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/NmsManager.java index 2232640f..7833682b 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/NmsManager.java @@ -1,27 +1,26 @@ package net.imprex.orebfuscator.nms.v1_20_R1; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -110,25 +117,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -166,7 +173,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/ReadOnlyChunkWrapper.java deleted file mode 100644 index 906c43cc..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_20_R1; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/RegionFileCache.java index c9d9189a..8200ba7e 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R1/src/main/java/net/imprex/orebfuscator/nms/v1_20_R1/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_20_R1; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/DefaultChunkAccessor.java new file mode 100644 index 00000000..eebb6b8e --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_20_R2; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/NmsManager.java index 3a112683..43160198 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/NmsManager.java @@ -1,27 +1,26 @@ package net.imprex.orebfuscator.nms.v1_20_R2; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -110,25 +117,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -166,7 +173,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/ReadOnlyChunkWrapper.java deleted file mode 100644 index 739b574e..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_20_R2; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/RegionFileCache.java index 0f65e500..f784d340 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R2/src/main/java/net/imprex/orebfuscator/nms/v1_20_R2/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_20_R2; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/DefaultChunkAccessor.java new file mode 100644 index 00000000..b73a3f5a --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_20_R3; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/NmsManager.java index 92c99455..5ea95c77 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/NmsManager.java @@ -1,27 +1,26 @@ package net.imprex.orebfuscator.nms.v1_20_R3; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -110,25 +117,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.left().orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -166,7 +173,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/ReadOnlyChunkWrapper.java deleted file mode 100644 index 2288e982..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_20_R3; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/RegionFileCache.java index 5c1a773e..a274cfff 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R3/src/main/java/net/imprex/orebfuscator/nms/v1_20_R3/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_20_R3; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/DefaultChunkAccessor.java new file mode 100644 index 00000000..68ef374c --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_20_R4; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/NmsManager.java index 18d807d5..d05090f4 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/NmsManager.java @@ -1,27 +1,26 @@ package net.imprex.orebfuscator.nms.v1_20_R4; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -110,25 +117,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -166,7 +173,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/ReadOnlyChunkWrapper.java deleted file mode 100644 index 09bb28f3..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_20_R4; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/RegionFileCache.java index b7032b0e..cb71ffa8 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_20_R4/src/main/java/net/imprex/orebfuscator/nms/v1_20_R4/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_20_R4; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/DefaultChunkAccessor.java new file mode 100644 index 00000000..e70a6ef2 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_21_R1; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/NmsManager.java index 7535db94..efa10a96 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/NmsManager.java @@ -1,27 +1,26 @@ package net.imprex.orebfuscator.nms.v1_21_R1; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import com.mojang.datafixers.util.Pair; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -38,15 +37,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -71,7 +77,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -80,6 +86,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -92,7 +99,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().map(Pair::getSecond).forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -110,25 +117,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -166,7 +173,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/ReadOnlyChunkWrapper.java deleted file mode 100644 index cbab6e42..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_21_R1; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/RegionFileCache.java index f3f23166..b6f262d6 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R1/src/main/java/net/imprex/orebfuscator/nms/v1_21_R1/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_21_R1; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/DefaultChunkAccessor.java new file mode 100644 index 00000000..6c62b108 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_21_R2; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/NmsManager.java index 231f7482..36819e99 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/NmsManager.java @@ -1,26 +1,25 @@ package net.imprex.orebfuscator.nms.v1_21_R2; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -36,15 +35,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -69,7 +75,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -78,6 +84,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender()) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -90,7 +97,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -108,25 +115,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -164,7 +171,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/ReadOnlyChunkWrapper.java deleted file mode 100644 index 7da016f6..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_21_R2; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/RegionFileCache.java index 2ffe052e..971c8b33 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R2/src/main/java/net/imprex/orebfuscator/nms/v1_21_R2/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_21_R2; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/DefaultChunkAccessor.java new file mode 100644 index 00000000..aa1fbc39 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_21_R3; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/NmsManager.java index 0ee52c66..1ff873dc 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/NmsManager.java @@ -1,26 +1,25 @@ package net.imprex.orebfuscator.nms.v1_21_R3; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -36,15 +35,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -69,7 +75,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -78,6 +84,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender()) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -90,7 +97,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -108,25 +115,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -164,7 +171,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/ReadOnlyChunkWrapper.java deleted file mode 100644 index 09a4e436..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_21_R3; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/RegionFileCache.java index 148f1a8e..b48ffbc7 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R3/src/main/java/net/imprex/orebfuscator/nms/v1_21_R3/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_21_R3; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/DefaultChunkAccessor.java new file mode 100644 index 00000000..0e9e1a36 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_21_R4; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/NmsManager.java index 3845684e..2388501d 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/NmsManager.java @@ -1,26 +1,25 @@ package net.imprex.orebfuscator.nms.v1_21_R4; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -36,15 +35,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -69,7 +75,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -78,6 +84,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender()) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -90,7 +97,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -108,25 +115,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -164,7 +171,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/ReadOnlyChunkWrapper.java deleted file mode 100644 index 62ded1a9..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_21_R4; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/RegionFileCache.java index 011c5f4f..d8799bba 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R4/src/main/java/net/imprex/orebfuscator/nms/v1_21_R4/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_21_R4; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/DefaultChunkAccessor.java new file mode 100644 index 00000000..1bfb23c3 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_21_R5; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/NmsManager.java index ba4cdedd..c8f15ae7 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/NmsManager.java @@ -1,26 +1,25 @@ package net.imprex.orebfuscator.nms.v1_21_R5; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -36,15 +35,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -69,7 +75,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -78,6 +84,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender()) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -90,7 +97,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -108,25 +115,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -164,7 +171,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/ReadOnlyChunkWrapper.java deleted file mode 100644 index 3d2e02f2..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_21_R5; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/RegionFileCache.java index 365fcb44..447a01fa 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R5/src/main/java/net/imprex/orebfuscator/nms/v1_21_R5/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_21_R5; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/pom.xml b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/pom.xml index 1626c36b..19686ccd 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/pom.xml +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/pom.xml @@ -1,4 +1,6 @@ - + 4.0.0 @@ -25,7 +27,7 @@ provided - + diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/DefaultChunkAccessor.java new file mode 100644 index 00000000..3e611308 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_21_R6; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/NmsManager.java index ea8f01ec..61b8964e 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/NmsManager.java @@ -1,26 +1,25 @@ package net.imprex.orebfuscator.nms.v1_21_R6; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import org.bukkit.World; -import org.bukkit.entity.Player; import com.google.common.collect.ImmutableList; import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -36,15 +35,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -69,7 +75,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().location().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -78,6 +84,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender()) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -90,7 +97,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.location().toString()))) @@ -108,25 +115,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -164,7 +171,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/ReadOnlyChunkWrapper.java deleted file mode 100644 index be1e2d6b..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_21_R6; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/RegionFileCache.java index da91ac3b..26c14f24 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R6/src/main/java/net/imprex/orebfuscator/nms/v1_21_R6/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_21_R6; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/pom.xml b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/pom.xml index 1b946ba0..ffa5485b 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/pom.xml +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/pom.xml @@ -1,4 +1,6 @@ - + 4.0.0 @@ -38,7 +40,8 @@ net.imprex.orebfuscator.nms.v1_21_R7 - net.imprex.orebfuscator.nms.v1_21_R7_mojang + net.imprex.orebfuscator.nms.v1_21_R7_mojang + @@ -56,7 +59,8 @@ remap-obf - org.spigotmc:minecraft-server:1.21.11-R0.1-SNAPSHOT:txt:maps-mojang + org.spigotmc:minecraft-server:1.21.11-R0.1-SNAPSHOT:txt:maps-mojang + true org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:jar:remapped-mojang @@ -76,7 +80,8 @@ ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar - org.spigotmc:minecraft-server:1.21.11-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:minecraft-server:1.21.11-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:jar:remapped-obf diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/DefaultChunkAccessor.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/DefaultChunkAccessor.java new file mode 100644 index 00000000..832d4552 --- /dev/null +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/DefaultChunkAccessor.java @@ -0,0 +1,12 @@ +package net.imprex.orebfuscator.nms.v1_21_R7; + +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import net.minecraft.world.level.chunk.ChunkAccess; + +public record DefaultChunkAccessor(ChunkAccess chunk) implements ChunkAccessor { + + @Override + public int getBlockState(int x, int y, int z) { + return NmsManager.getBlockState(chunk, x, y, z); + } +} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/NmsManager.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/NmsManager.java index 4eceab57..45f12a2a 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/NmsManager.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/NmsManager.java @@ -1,29 +1,25 @@ package net.imprex.orebfuscator.nms.v1_21_R7; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.bukkit.World; -import org.bukkit.entity.Player; - import com.google.common.collect.ImmutableList; - import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; import dev.imprex.orebfuscator.config.api.Config; +import dev.imprex.orebfuscator.interop.ChunkAccessor; import dev.imprex.orebfuscator.util.BlockProperties; import dev.imprex.orebfuscator.util.BlockStateProperties; import dev.imprex.orebfuscator.util.BlockTag; import dev.imprex.orebfuscator.util.NamespacedKey; import it.unimi.dsi.fastutil.shorts.Short2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import net.imprex.orebfuscator.nms.AbstractNmsManager; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; @@ -39,15 +35,22 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class NmsManager extends AbstractNmsManager { private static final int BLOCK_ID_AIR = Block.getId(Blocks.AIR.defaultBlockState()); - static int getBlockState(LevelChunk chunk, int x, int y, int z) { - LevelChunkSection[] sections = chunk.getSections(); + static int getBlockState(ChunkAccess chunk, int x, int y, int z) { + @Nullable LevelChunkSection[] sections = chunk.getSections(); int sectionIndex = chunk.getSectionIndex(y); if (sectionIndex >= 0 && sectionIndex < sections.length) { @@ -72,7 +75,7 @@ public NmsManager() { super(Block.BLOCK_STATE_REGISTRY.size()); for (Map.Entry, Block> entry : BuiltInRegistries.BLOCK.entrySet()) { - NamespacedKey namespacedKey = NamespacedKey.fromString(entry.getKey().identifier().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(entry.getKey().identifier().toString()); Block block = entry.getValue(); ImmutableList possibleBlockStates = block.getStateDefinition().getPossibleStates(); @@ -81,6 +84,7 @@ public NmsManager() { for (BlockState blockState : possibleBlockStates) { BlockStateProperties properties = BlockStateProperties.builder(Block.getId(blockState)) .withIsAir(blockState.isAir()) + .withIsLava(block == Blocks.LAVA) .withIsOccluding(blockState.isSolidRender()) .withIsBlockEntity(blockState.hasBlockEntity()) .withIsDefaultState(Objects.equals(block.defaultBlockState(), blockState)) @@ -93,7 +97,7 @@ public NmsManager() { } BuiltInRegistries.BLOCK.getTags().forEach(tag -> { - NamespacedKey namespacedKey = NamespacedKey.fromString(tag.key().location().toString()); + NamespacedKey namespacedKey = NamespacedKey.parse(tag.key().location().toString()); Set blocks = tag.stream() .map(holder -> holder.unwrapKey().map(key -> getBlockByName(key.identifier().toString()))) @@ -111,25 +115,25 @@ public AbstractRegionFileCache createRegionFileCache(Config config) { } @Override - public ReadOnlyChunk getReadOnlyChunk(World world, int chunkX, int chunkZ) { - ServerChunkCache serverChunkCache = level(world).getChunkSource(); - LevelChunk chunk = serverChunkCache.getChunk(chunkX, chunkZ, true); - return new ReadOnlyChunkWrapper(chunk); + public CompletableFuture<@Nullable ChunkAccessor> getChunkFuture(World world, int chunkX, int chunkZ) { + return level(world).getChunkSource() + .getChunkFuture(chunkX, chunkZ, ChunkStatus.FULL, true) + .thenApply(result -> { + var chunk = result.orElse(null); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; + }); } @Override - public int getBlockState(World world, int x, int y, int z) { + public @Nullable ChunkAccessor getChunkNow(World world, int chunkX, int chunkZ) { ServerChunkCache serverChunkCache = level(world).getChunkSource(); - if (!serverChunkCache.isChunkLoaded(x >> 4, z >> 4)) { - return BLOCK_ID_AIR; - } - LevelChunk chunk = serverChunkCache.getChunk(x >> 4, z >> 4, true); - if (chunk == null) { - return BLOCK_ID_AIR; + LevelChunk chunk = serverChunkCache.getChunkNow(chunkX, chunkZ); + if (chunk == null && serverChunkCache.isChunkLoaded(chunkX, chunkZ)) { + chunk = serverChunkCache.getChunk(chunkX, chunkZ, false); } - return getBlockState(chunk, x, y, z); + return chunk != null ? new DefaultChunkAccessor(chunk) : null; } @Override @@ -167,7 +171,10 @@ public void sendBlockUpdates(Player player, Iterable packet = blockEntity.getUpdatePacket(); + if (packet != null) { + blockEntityPackets.add(packet); + } } } } diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/ReadOnlyChunkWrapper.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/ReadOnlyChunkWrapper.java deleted file mode 100644 index 9df0f7c8..00000000 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/ReadOnlyChunkWrapper.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.imprex.orebfuscator.nms.v1_21_R7; - -import net.imprex.orebfuscator.nms.ReadOnlyChunk; -import net.minecraft.world.level.chunk.LevelChunk; - -public class ReadOnlyChunkWrapper implements ReadOnlyChunk { - - private final LevelChunk chunk; - - ReadOnlyChunkWrapper(LevelChunk chunk) { - this.chunk = chunk; - } - - @Override - public int getBlockState(int x, int y, int z) { - return NmsManager.getBlockState(chunk, x, y, z); - } -} diff --git a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/RegionFileCache.java b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/RegionFileCache.java index be2fc633..70c2b687 100644 --- a/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/RegionFileCache.java +++ b/orebfuscator-nms/orebfuscator-nms-v1_21_R7/src/main/java/net/imprex/orebfuscator/nms/v1_21_R7/RegionFileCache.java @@ -1,20 +1,21 @@ package net.imprex.orebfuscator.nms.v1_21_R7; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.config.api.CacheConfig; +import dev.imprex.orebfuscator.util.ChunkCacheKey; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.file.Path; - -import org.bukkit.Bukkit; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionFileVersion; +import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class RegionFileCache extends AbstractRegionFileCache { RegionFileCache(CacheConfig cacheConfig) { @@ -33,7 +34,7 @@ protected void closeRegionFile(RegionFile t) throws IOException { } @Override - protected DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { + protected @Nullable DataInputStream createInputStream(RegionFile t, ChunkCacheKey key) throws IOException { return t.getChunkDataInputStream(new ChunkPos(key.x(), key.z())); } diff --git a/orebfuscator-nms/pom.xml b/orebfuscator-nms/pom.xml index a3508500..3ef15021 100644 --- a/orebfuscator-nms/pom.xml +++ b/orebfuscator-nms/pom.xml @@ -14,8 +14,6 @@ orebfuscator-nms-api - orebfuscator-nms-v1_16_R1 - orebfuscator-nms-v1_16_R2 orebfuscator-nms-v1_16_R3 orebfuscator-nms-v1_17_R1 orebfuscator-nms-v1_18_R1 diff --git a/orebfuscator-plugin/pom.xml b/orebfuscator-plugin/pom.xml index 31b7f455..2262b336 100644 --- a/orebfuscator-plugin/pom.xml +++ b/orebfuscator-plugin/pom.xml @@ -98,7 +98,7 @@ compile - org.lz4 + at.yawk.lz4 lz4-java ${dependency.lz4.version} compile @@ -136,7 +136,7 @@ joml - org.lz4 + at.yawk.lz4 lz4-java @@ -175,18 +175,6 @@ ${revision} compile - - net.imprex - orebfuscator-nms-v1_16_R1 - ${revision} - compile - - - net.imprex - orebfuscator-nms-v1_16_R2 - ${revision} - compile - net.imprex orebfuscator-nms-v1_16_R3 diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/DefaultOrebfuscatorService.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/DefaultOrebfuscatorService.java index 1aec2fd7..8ff75ae9 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/DefaultOrebfuscatorService.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/DefaultOrebfuscatorService.java @@ -2,13 +2,13 @@ import java.util.Collection; import java.util.Iterator; - -import org.bukkit.World; -import org.bukkit.block.Block; - import net.imprex.orebfuscator.api.OrebfuscatorService; import net.imprex.orebfuscator.obfuscation.ObfuscationSystem; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.jspecify.annotations.NullMarked; +@NullMarked public final class DefaultOrebfuscatorService implements OrebfuscatorService { private final ObfuscationSystem obfuscationSystem; diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/MetricsSystem.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/MetricsSystem.java index 86d0ccd3..acf7f3ae 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/MetricsSystem.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/MetricsSystem.java @@ -1,18 +1,18 @@ package net.imprex.orebfuscator; +import dev.imprex.orebfuscator.config.OrebfuscatorConfig; +import dev.imprex.orebfuscator.util.QuickMaths; import java.util.HashMap; import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; - import org.bstats.bukkit.Metrics; import org.bstats.charts.DrilldownPie; import org.bstats.charts.SimplePie; import org.bukkit.Bukkit; +import org.jspecify.annotations.NullMarked; -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; -import dev.imprex.orebfuscator.util.MathUtil; - +@NullMarked public class MetricsSystem { private static final NavigableMap PLAYER_COUNT_GROUPS = new TreeMap<>(); @@ -32,8 +32,8 @@ public MetricsSystem(Orebfuscator orebfuscator) { this.metrics = new Metrics(orebfuscator, 8942); this.addMemoryChart(); this.addPlayerCountChart(); - this.addConfigCharts(orebfuscator.getOrebfuscatorConfig()); - this.addUsageCharts(orebfuscator.getOrebfuscatorConfig()); + this.addConfigCharts(orebfuscator.config()); + this.addUsageCharts(orebfuscator.config()); } public void addMemoryChart() { @@ -47,7 +47,7 @@ public void addMemoryChart() { } else { float gibiByte = Math.round(memory / 1073741824f * 100f) / 100f; exact.put(gibiByte + "GiB", 1); - result.put(MathUtil.ceilToPowerOfTwo((int) gibiByte) + "GiB", exact); + result.put(QuickMaths.ceilToPowerOfTwo((int) gibiByte) + "GiB", exact); } return result; @@ -62,8 +62,8 @@ public void addPlayerCountChart() { } public void addConfigCharts(OrebfuscatorConfig config) { - this.metrics.addCustomChart(new SimplePie("max_mspt", () -> { - return Integer.toString(config.advanced().maxMillisecondsPerTick()); + this.metrics.addCustomChart(new SimplePie("obfuscation_timeout", () -> { + return Long.toString(config.advanced().obfuscationTimeout()); })); this.metrics.addCustomChart(new SimplePie("update_radius", () -> { return Integer.toString(config.general().updateRadius()); @@ -78,7 +78,10 @@ public void addUsageCharts(OrebfuscatorConfig config) { return Boolean.toString(config.general().ignoreSpectator()); })); this.metrics.addCustomChart(new SimplePie("cache", () -> { - return Boolean.toString(config.cache().enabled()); + if (config.cache().enabled()) { + return config.cache().enableDiskCache() ? "disk" : "true"; + } + return "false"; })); this.metrics.addCustomChart(new SimplePie("proximity", () -> { return Boolean.toString(config.proximityEnabled()); @@ -89,8 +92,6 @@ public void addUsageCharts(OrebfuscatorConfig config) { this.metrics.addCustomChart(new SimplePie("frustum_culling", () -> { return Boolean.toString(config.usesFrustumCulling()); })); - this.metrics.addCustomChart(new SimplePie("ray_cast", () -> { - return config.usesRayCastCheck(); - })); + this.metrics.addCustomChart(new SimplePie("ray_cast", config::usesRayCastCheck)); } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/Orebfuscator.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/Orebfuscator.java index 32831d6c..fa26bf63 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/Orebfuscator.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/Orebfuscator.java @@ -1,104 +1,111 @@ package net.imprex.orebfuscator; -import java.nio.file.Path; -import java.util.List; - -import org.bukkit.Bukkit; -import org.bukkit.event.Event; -import org.bukkit.event.EventPriority; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.server.PluginEnableEvent; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.ServicePriority; -import org.bukkit.plugin.java.JavaPlugin; - +import dev.imprex.orebfuscator.UpdateSystem; +import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; +import dev.imprex.orebfuscator.cache.ObfuscationCache; import dev.imprex.orebfuscator.chunk.ChunkFactory; import dev.imprex.orebfuscator.config.OrebfuscatorConfig; +import dev.imprex.orebfuscator.interop.OrebfuscatorCore; +import dev.imprex.orebfuscator.interop.PlayerAccessor; import dev.imprex.orebfuscator.interop.RegistryAccessor; -import dev.imprex.orebfuscator.interop.ServerAccessor; import dev.imprex.orebfuscator.interop.WorldAccessor; import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.obfuscation.ObfuscationPipeline; +import dev.imprex.orebfuscator.obfuscation.ObfuscationProcessor; +import dev.imprex.orebfuscator.proximity.ProximitySystem; +import dev.imprex.orebfuscator.statistics.OrebfuscatorStatistics; +import dev.imprex.orebfuscator.statistics.StatisticsRegistry; import dev.imprex.orebfuscator.util.Version; +import dev.imprex.orebfuscator.util.concurrent.OrebfuscatorExecutor; +import java.nio.file.Path; +import java.util.List; import net.imprex.orebfuscator.api.OrebfuscatorService; -import net.imprex.orebfuscator.cache.ObfuscationCache; import net.imprex.orebfuscator.iterop.BukkitLoggerAccessor; -import net.imprex.orebfuscator.iterop.BukkitWorldAccessor; +import net.imprex.orebfuscator.iterop.BukkitPlayerAccessorManager; +import net.imprex.orebfuscator.iterop.BukkitWorldAccessorManager; import net.imprex.orebfuscator.obfuscation.ObfuscationSystem; -import net.imprex.orebfuscator.player.OrebfuscatorPlayerMap; -import net.imprex.orebfuscator.proximity.ProximityDirectorThread; import net.imprex.orebfuscator.proximity.ProximityPacketListener; import net.imprex.orebfuscator.util.MinecraftVersion; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; -public class Orebfuscator extends JavaPlugin implements Listener, ServerAccessor { +public class Orebfuscator extends JavaPlugin implements Listener, OrebfuscatorCore { public static final ThreadGroup THREAD_GROUP = new ThreadGroup("orebfuscator"); - private OrebfuscatorStatistics statistics; + private StatisticsRegistry statisticsRegistry; private OrebfuscatorConfig config; - private OrebfuscatorPlayerMap playerMap; - private UpdateSystem updateSystem; + private OrebfuscatorStatistics statistics; + private OrebfuscatorExecutor executor; + + private BukkitWorldAccessorManager worldManager; + private BukkitPlayerAccessorManager playerManager; + + private ChunkFactory chunkFactory; + private ObfuscationProcessor obfuscationProcessor; private ObfuscationCache obfuscationCache; + private ObfuscationPipeline obfuscationPipeline; private ObfuscationSystem obfuscationSystem; - private ProximityDirectorThread proximityThread; + + private ProximitySystem proximitySystem; private ProximityPacketListener proximityPacketListener; - private ChunkFactory chunkFactory; + + private Version orebfuscatorVersion; + private UpdateSystem updateSystem; @Override public void onLoad() { OfcLogger.setLogger(new BukkitLoggerAccessor(getLogger())); + this.orebfuscatorVersion = Version.parse(getDescription().getVersion()); } @Override public void onEnable() { try { - // Check for valid minecraft version if (MinecraftVersion.isBelow("1.16")) { throw new RuntimeException("Orebfuscator only supports minecraft 1.16 and above"); } - // Check if protocolLib is enabled Plugin protocolLib = getServer().getPluginManager().getPlugin("ProtocolLib"); if (protocolLib == null || !protocolLib.isEnabled()) { throw new RuntimeException("ProtocolLib can't be found or is disabled! Orebfuscator can't be enabled."); } - BukkitWorldAccessor.registerListener(this); + this.statisticsRegistry = new StatisticsRegistry(); + this.worldManager = new BukkitWorldAccessorManager(this); - this.statistics = new OrebfuscatorStatistics(); - - // Load configurations OrebfuscatorNms.initialize(); this.config = new OrebfuscatorConfig(this); OrebfuscatorCompatibility.initialize(this, config); - this.playerMap = new OrebfuscatorPlayerMap(this); - - // Initialize metrics + this.playerManager = new BukkitPlayerAccessorManager(this); new MetricsSystem(this); + this.updateSystem = new UpdateSystem(this, "bukkit"); - // initialize update system and check for updates - this.updateSystem = new UpdateSystem(this); + this.statistics = new OrebfuscatorStatistics(this.config, this.statisticsRegistry); + this.executor = new OrebfuscatorExecutor(this); - // Load chunk cache - this.obfuscationCache = new ObfuscationCache(this); - - // Load obfuscater this.chunkFactory = new ChunkFactory(this); + this.obfuscationProcessor = new ObfuscationProcessor(this); + this.obfuscationCache = new ObfuscationCache(this); + this.obfuscationPipeline = new ObfuscationPipeline(this); this.obfuscationSystem = new ObfuscationSystem(this); - // Load proximity hider - this.proximityThread = new ProximityDirectorThread(this); + this.proximitySystem = new ProximitySystem(this); if (this.config.proximityEnabled()) { - this.proximityThread.start(); - + this.proximitySystem.start(); this.proximityPacketListener = new ProximityPacketListener(this); } // Load packet listener this.obfuscationSystem.registerChunkListener(); - - // Store formatted config this.config.store(); // initialize service @@ -119,6 +126,10 @@ public void onEnable() { @Override public void onDisable() { + if (this.executor != null) { + this.executor.shutdown(); + } + if (this.obfuscationCache != null) { this.obfuscationCache.close(); } @@ -127,10 +138,8 @@ public void onDisable() { this.obfuscationSystem.shutdown(); } - if (this.config != null && this.config.proximityEnabled() && this.proximityPacketListener != null - && this.proximityThread != null) { + if (this.config != null && this.config.proximityEnabled() && this.proximityPacketListener != null) { this.proximityPacketListener.unregister(); - this.proximityThread.close(); } OrebfuscatorCompatibility.close(); @@ -147,67 +156,115 @@ public void onEnableFailed(Listener listener, Event event) { } } - public OrebfuscatorStatistics getStatistics() { - return statistics; + public UpdateSystem updateSystem() { + return updateSystem; } - public OrebfuscatorConfig getOrebfuscatorConfig() { - return this.config; + public ObfuscationSystem getObfuscationSystem() { + return obfuscationSystem; } - public OrebfuscatorPlayerMap getPlayerMap() { - return playerMap; + public BukkitWorldAccessorManager worldManager() { + return worldManager; } - public UpdateSystem getUpdateSystem() { - return updateSystem; + public BukkitPlayerAccessorManager playerManager() { + return playerManager; } - public ObfuscationCache getObfuscationCache() { - return this.obfuscationCache; + @Override + public OrebfuscatorExecutor executor() { + return executor; } - public ObfuscationSystem getObfuscationSystem() { - return obfuscationSystem; + @Override + public StatisticsRegistry statisticsRegistry() { + return statisticsRegistry; } - public ProximityPacketListener getProximityPacketListener() { - return this.proximityPacketListener; + @Override + public OrebfuscatorStatistics statistics() { + return statistics; + } + + @Override + public OrebfuscatorConfig config() { + return config; } - public ChunkFactory getChunkFactory() { + @Override + public ChunkFactory chunkFactory() { return chunkFactory; } @Override - public Path getConfigDirectory() { + public ObfuscationCache cache() { + return obfuscationCache; + } + + @Override + public ObfuscationPipeline obfuscationPipeline() { + return obfuscationPipeline; + } + + @Override + public ObfuscationProcessor obfuscationProcessor() { + return obfuscationProcessor; + } + + @Override + public boolean isGameThread() { + return OrebfuscatorCompatibility.isGameThread(); + } + + @Override + public Path configDirectory() { return getDataFolder().toPath(); } @Override - public Path getWorldDirectory() { + public Path worldDirectory() { return Bukkit.getWorldContainer().toPath(); } @Override - public String getOrebfuscatorVersion() { - return getDescription().getVersion(); + public String name() { + return getDescription().getName(); + } + + @Override + public Version orebfuscatorVersion() { + return this.orebfuscatorVersion; } @Override - public Version getMinecraftVersion() { + public Version minecraftVersion() { return MinecraftVersion.current(); } @Override - public RegistryAccessor getRegistry() { + public RegistryAccessor registry() { return OrebfuscatorNms.registry(); } @Override - public List getWorlds() { - return BukkitWorldAccessor.getWorlds().stream() - .map(WorldAccessor.class::cast) - .toList(); + public AbstractRegionFileCache createRegionFileCache() { + return OrebfuscatorNms.createRegionFileCache(config); + } + + @Override + public List worlds() { + return this.worldManager.all(); + } + + @Override + public List players() { + return this.playerManager.all(); + } + + @Override + public String toString() { + var meta = getDescription(); + return String.format("%s %s", meta.getName(), meta.getVersion()); } } \ No newline at end of file diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/OrebfuscatorCommand.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/OrebfuscatorCommand.java index 6797f618..065097b5 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/OrebfuscatorCommand.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/OrebfuscatorCommand.java @@ -1,46 +1,26 @@ package net.imprex.orebfuscator; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.PacketListener; +import dev.imprex.orebfuscator.OrebfuscatorDumpFile; +import dev.imprex.orebfuscator.PermissionRequirements; import java.nio.file.Path; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.TemporalAccessor; -import java.util.Arrays; -import java.util.Base64; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; - +import net.imprex.orebfuscator.util.MinecraftVersion; +import net.imprex.orebfuscator.util.PermissionUtil; import org.bukkit.Bukkit; -import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabCompleter; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.events.PacketListener; -import com.google.gson.JsonObject; -import com.google.gson.internal.Streams; -import com.google.gson.stream.JsonWriter; - -import dev.imprex.orebfuscator.logging.OfcLogger; -import dev.imprex.orebfuscator.util.JavaVersion; -import net.imprex.orebfuscator.iterop.BukkitWorldAccessor; -import net.imprex.orebfuscator.util.MinecraftVersion; -import net.imprex.orebfuscator.util.PermissionUtil; +import org.jspecify.annotations.NonNull; public class OrebfuscatorCommand implements CommandExecutor, TabCompleter { - private static final List TAB_COMPLETE = Arrays.asList("dump"); - - private final DateTimeFormatter fileFormat = DateTimeFormatter.ofPattern("uuuu-MM-dd_HH.mm.ss"); - private final DateTimeFormatter timeFormat = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + private static final List TAB_COMPLETE = List.of("dump"); private final Orebfuscator orebfuscator; @@ -49,94 +29,48 @@ public OrebfuscatorCommand(Orebfuscator orebfuscator) { } @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + public boolean onCommand(@NonNull CommandSender sender, Command command, @NonNull String label, String[] args) { if (!command.getName().equalsIgnoreCase("orebfuscator")) { sender.sendMessage("Incorrect command registered!"); return false; } - if (!PermissionUtil.canAccessAdminTools(sender)) { + if (!PermissionUtil.hasPermission(sender, PermissionRequirements.ADMIN)) { sender.sendMessage("You don't have the 'orebfuscator.admin' permission."); return false; } if (args.length == 0) { - sender.sendMessage("You are using " + this.orebfuscator.toString()); - sender.sendMessage(this.orebfuscator.getStatistics().toString()); + sender.sendMessage( + "You are using %s %s".formatted(this.orebfuscator.name(), this.orebfuscator.orebfuscatorVersion())); + sender.sendMessage(this.orebfuscator.statisticsRegistry().format()); } else if (args[0].equalsIgnoreCase("dump")) { - TemporalAccessor now = OffsetDateTime.now(ZoneOffset.UTC); + var dumpFile = new OrebfuscatorDumpFile(this.orebfuscator); - JsonObject root = new JsonObject(); - root.addProperty("timestamp", timeFormat.format(now)); + dumpFile.set("versions.nms", MinecraftVersion.nmsVersion()); + dumpFile.set("versions.server", Bukkit.getVersion()); + dumpFile.set("versions.bukkit", Bukkit.getBukkitVersion()); + dumpFile.set("versions.protocolLib", ProtocolLibrary.getPlugin().toString()); - JsonObject versions = new JsonObject(); - versions.addProperty("java", Integer.toString(JavaVersion.get())); - versions.addProperty("nms", MinecraftVersion.nmsVersion()); - versions.addProperty("server", Bukkit.getVersion()); - versions.addProperty("bukkit", Bukkit.getBukkitVersion()); - versions.addProperty("protocolLib", ProtocolLibrary.getPlugin().toString()); - versions.addProperty("orebfuscator", orebfuscator.toString()); - root.add("versions", versions); - - root.add("statistics", orebfuscator.getStatistics().toJson()); - - JsonObject plugins = new JsonObject(); + var plugins = dumpFile.createSection("plugins"); for (Plugin bukkitPlugin : Bukkit.getPluginManager().getPlugins()) { PluginDescriptionFile description = bukkitPlugin.getDescription(); - JsonObject plugin = new JsonObject(); - plugin.addProperty("version", description.getVersion()); - plugin.addProperty("author", description.getAuthors().toString()); - plugins.add(bukkitPlugin.getName(), plugin); - } - root.add("plugins", plugins); - JsonObject worlds = new JsonObject(); - for (World bukkitWorld : Bukkit.getWorlds()) { - JsonObject world = new JsonObject(); - world.addProperty("uuid", bukkitWorld.getUID().toString()); - world.addProperty("heightAccessor", BukkitWorldAccessor.get(bukkitWorld).toString()); - worlds.add(bukkitWorld.getName(), world); + var plugin = plugins.createSection(bukkitPlugin.getName()); + plugin.set("version", description.getVersion()); + plugin.set("author", description.getAuthors().toString()); } - root.add("worlds", worlds); - JsonObject listeners = new JsonObject(); + var listeners = dumpFile.createSection("listeners"); for (PacketListener packetListener : ProtocolLibrary.getProtocolManager().getPacketListeners()) { - JsonObject listener = new JsonObject(); - listener.addProperty("plugin", packetListener.getPlugin().toString()); - listener.addProperty("receivingWhitelist", packetListener.getSendingWhitelist().toString()); - listener.addProperty("sendingWhitelist", packetListener.getSendingWhitelist().toString()); - String key = packetListener.getClass().toGenericString() + "@" + System.identityHashCode(packetListener); - listeners.add(key, listener); - } - root.add("listeners", listeners); - - root.add("blocks", orebfuscator.getOrebfuscatorConfig().toJson()); - - Base64.Encoder encoder = Base64.getUrlEncoder(); - - String latestLog = OfcLogger.getLatestVerboseLog(); - root.addProperty("verboseLog", encoder.encodeToString(latestLog.getBytes(StandardCharsets.UTF_8))); - - try { - Path configPath = orebfuscator.getDataFolder().toPath().resolve("config.yml"); - String config = Files.readAllLines(configPath).stream().collect(Collectors.joining("\n")); - root.addProperty("config", encoder.encodeToString(config.getBytes(StandardCharsets.UTF_8))); - } catch (IOException e) { - e.printStackTrace(); - } - - String configReport = orebfuscator.getOrebfuscatorConfig().report(); - configReport = configReport != null ? configReport : ""; - root.addProperty("configReport", encoder.encodeToString(configReport.getBytes(StandardCharsets.UTF_8))); - - Path path = orebfuscator.getDataFolder().toPath().resolve("dump-" + fileFormat.format(now) + ".json"); - try (JsonWriter writer = new JsonWriter(Files.newBufferedWriter(path))) { - writer.setIndent(" "); - Streams.write(root, writer); - } catch (IOException e) { - e.printStackTrace(); + var listener = listeners.createSection( + packetListener.getClass().toGenericString() + "@" + System.identityHashCode(packetListener)); + listener.set("plugin", packetListener.getPlugin().toString()); + listener.set("receivingWhitelist", packetListener.getReceivingWhitelist().toString()); + listener.set("sendingWhitelist", packetListener.getSendingWhitelist().toString()); } + Path path = dumpFile.write(); sender.sendMessage("Dump file created at: " + path); } else { return false; @@ -146,7 +80,8 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } @Override - public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + public List onTabComplete(@NonNull CommandSender sender, @NonNull Command command, @NonNull String alias, + String[] args) { return args.length == 1 ? TAB_COMPLETE : Collections.emptyList(); } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/OrebfuscatorStatistics.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/OrebfuscatorStatistics.java deleted file mode 100644 index 849eda64..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/OrebfuscatorStatistics.java +++ /dev/null @@ -1,200 +0,0 @@ -package net.imprex.orebfuscator; - -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.LongSupplier; - -import com.google.gson.JsonObject; - -public class OrebfuscatorStatistics { - - private static String formatPrecent(double percent) { - return String.format("%.2f%%", percent * 100); - } - - private static String formatNanos(long time) { - if (time > 1000_000L) { - return String.format("%.1fms", time / 1000_000d); - } else if (time > 1000L) { - return String.format("%.1fµs", time / 1000d); - } else { - return String.format("%dns", time); - } - } - - private static String formatBytes(long bytes) { - if (bytes > 1073741824L) { - return String.format("%.1f GiB", bytes / 1073741824d); - } else if (bytes > 1048576L) { - return String.format("%.1f MiB", bytes / 1048576d); - } else if (bytes > 1024L) { - return String.format("%.1f KiB", bytes / 1024d); - } else { - return String.format("%d B", bytes); - } - } - - private final AtomicLong cacheHitCountMemory = new AtomicLong(0); - private final AtomicLong cacheHitCountDisk = new AtomicLong(0); - private final AtomicLong cacheMissCount = new AtomicLong(0); - private final AtomicLong cacheEstimatedSize = new AtomicLong(0); - private LongSupplier memoryCacheSize = () -> 0; - private LongSupplier diskCacheQueueLength = () -> 0; - private LongSupplier obfuscationQueueLength = () -> 0; - private LongSupplier obfuscationWaitTime = () -> 0; - private LongSupplier obfuscationProcessTime = () -> 0; - private LongSupplier proximityWaitTime = () -> 0; - private LongSupplier proximityProcessTime = () -> 0; - private LongSupplier originalChunkSize = () -> 0; - private LongSupplier obfuscatedChunkSize = () -> 0; - - public void onCacheHitMemory() { - this.cacheHitCountMemory.incrementAndGet(); - } - - public void onCacheHitDisk() { - this.cacheHitCountDisk.incrementAndGet(); - } - - public void onCacheMiss() { - this.cacheMissCount.incrementAndGet(); - } - - public void onCacheSizeChange(int delta) { - this.cacheEstimatedSize.addAndGet(delta); - } - - public void setMemoryCacheSizeSupplier(LongSupplier supplier) { - this.memoryCacheSize = Objects.requireNonNull(supplier); - } - - public void setDiskCacheQueueLengthSupplier(LongSupplier supplier) { - this.diskCacheQueueLength = Objects.requireNonNull(supplier); - } - - public void setObfuscationQueueLengthSupplier(LongSupplier supplier) { - this.obfuscationQueueLength = Objects.requireNonNull(supplier); - } - - public void setObfuscationWaitTime(LongSupplier supplier) { - this.obfuscationWaitTime = Objects.requireNonNull(supplier); - } - - public void setObfuscationProcessTime(LongSupplier supplier) { - this.obfuscationProcessTime = Objects.requireNonNull(supplier); - } - - public void setProximityWaitTime(LongSupplier supplier) { - this.proximityWaitTime = Objects.requireNonNull(supplier); - } - - public void setProximityProcessTime(LongSupplier supplier) { - this.proximityProcessTime = Objects.requireNonNull(supplier); - } - - public void setOriginalChunkSize(LongSupplier supplier) { - this.originalChunkSize = Objects.requireNonNull(supplier); - } - - public void setObfuscatedChunkSize(LongSupplier supplier) { - this.obfuscatedChunkSize = Objects.requireNonNull(supplier); - } - - @Override - public String toString() { - long cacheHitCountMemory = this.cacheHitCountMemory.get(); - long cacheHitCountDisk = this.cacheHitCountDisk.get(); - long cacheMissCount = this.cacheMissCount.get(); - long cacheEstimatedSize = this.cacheEstimatedSize.get(); - long memoryCacheSize = this.memoryCacheSize.getAsLong(); - long diskCacheQueueLength = this.diskCacheQueueLength.getAsLong(); - long obfuscationQueueLength = this.obfuscationQueueLength.getAsLong(); - - double totalCacheRequest = (double) (cacheHitCountMemory + cacheHitCountDisk + cacheMissCount); - - double memoryCacheHitRate = 0.0d; - double diskCacheHitRate = 0.0d; - if (totalCacheRequest > 0) { - memoryCacheHitRate = (double) cacheHitCountMemory / totalCacheRequest; - diskCacheHitRate = (double) cacheHitCountDisk / totalCacheRequest; - } - - long memoryCacheBytesPerEntry = 0; - if (memoryCacheSize > 0) { - memoryCacheBytesPerEntry = cacheEstimatedSize / memoryCacheSize; - } - - StringBuilder builder = new StringBuilder("Here are some useful statistics:\n"); - - builder.append(" - memoryCacheHitRate: ").append(formatPrecent(memoryCacheHitRate)).append('\n'); - builder.append(" - diskCacheHitRate: ").append(formatPrecent(diskCacheHitRate)).append('\n'); - builder.append(" - memoryCacheEstimatedSize: ").append(formatBytes(cacheEstimatedSize)).append('\n'); - builder.append(" - memoryCacheBytesPerEntry: ").append(formatBytes(memoryCacheBytesPerEntry)).append('\n'); - builder.append(" - memoryCacheEntries: ").append(memoryCacheSize).append('\n'); - builder.append(" - diskCacheQueueLength: ").append(diskCacheQueueLength).append('\n'); - builder.append(" - obfuscationQueueLength: ").append(obfuscationQueueLength).append('\n'); - - long obfuscationWaitTime = this.obfuscationWaitTime.getAsLong(); - long obfuscationProcessTime = this.obfuscationProcessTime.getAsLong(); - long obfuscationTotalTime = obfuscationWaitTime + obfuscationProcessTime; - - double obfuscationUtilization = 0; - if (obfuscationTotalTime > 0) { - obfuscationUtilization = (double) obfuscationProcessTime / obfuscationTotalTime; - } - - builder.append(" - obfuscation (wait/process/utilization): ") - .append(formatNanos(obfuscationWaitTime)).append(" | ") - .append(formatNanos(obfuscationProcessTime)).append(" | ") - .append(formatPrecent(obfuscationUtilization)).append('\n'); - - long proximityWaitTime = this.proximityWaitTime.getAsLong(); - long proximityProcessTime = this.proximityProcessTime.getAsLong(); - long proximityTotalTime = proximityWaitTime + proximityProcessTime; - - double proximityUtilization = 0; - if (proximityTotalTime > 0) { - proximityUtilization = (double) proximityProcessTime / proximityTotalTime; - } - - builder.append(" - proximity (wait/process/utilization): ") - .append(formatNanos(proximityWaitTime)).append(" | ") - .append(formatNanos(proximityProcessTime)).append(" | ") - .append(formatPrecent(proximityUtilization)).append('\n'); - - long originalChunkSize = this.originalChunkSize.getAsLong(); - long obfuscatedChunkSize = this.obfuscatedChunkSize.getAsLong(); - - double ratio = 1; - if (originalChunkSize > 0) { - ratio = (double) obfuscatedChunkSize / originalChunkSize; - } - - builder.append(" - chunk size (original/obfuscated/ratio): ") - .append(formatBytes(originalChunkSize)).append(" | ") - .append(formatBytes(obfuscatedChunkSize)).append(" | ") - .append(formatPrecent(ratio)).append('\n'); - - return builder.toString(); - } - - public JsonObject toJson() { - JsonObject object = new JsonObject(); - - object.addProperty("cacheHitCountMemory", this.cacheHitCountMemory.get()); - object.addProperty("cacheHitCountDisk", this.cacheHitCountDisk.get()); - object.addProperty("cacheMissCount", this.cacheMissCount.get()); - object.addProperty("cacheEstimatedSize", this.cacheEstimatedSize.get()); - object.addProperty("memoryCacheSize", this.memoryCacheSize.getAsLong()); - object.addProperty("diskCacheQueueLength", this.diskCacheQueueLength.getAsLong()); - object.addProperty("obfuscationQueueLength", this.obfuscationQueueLength.getAsLong()); - object.addProperty("obfuscationWaitTime", this.obfuscationWaitTime.getAsLong()); - object.addProperty("obfuscationProcessTime", this.obfuscationProcessTime.getAsLong()); - object.addProperty("proximityWaitTime", this.proximityWaitTime.getAsLong()); - object.addProperty("proximityProcessTime", this.proximityProcessTime.getAsLong()); - object.addProperty("originalChunkSize", this.originalChunkSize.getAsLong()); - object.addProperty("obfuscatedChunkSize", this.obfuscatedChunkSize.getAsLong()); - - return object; - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/CacheChunkEntry.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/CacheChunkEntry.java deleted file mode 100644 index 7458ad49..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/CacheChunkEntry.java +++ /dev/null @@ -1,110 +0,0 @@ -package net.imprex.orebfuscator.cache; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; - -import dev.imprex.orebfuscator.util.BlockPos; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.imprex.orebfuscator.obfuscation.ObfuscationRequest; -import net.imprex.orebfuscator.obfuscation.ObfuscationResult; -import net.jpountz.lz4.LZ4BlockInputStream; -import net.jpountz.lz4.LZ4BlockOutputStream; - -public class CacheChunkEntry { - - public static CacheChunkEntry create(ObfuscationResult result) { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - - try ( - LZ4BlockOutputStream lz4BlockOutputStream = new LZ4BlockOutputStream(byteArrayOutputStream); - DataOutputStream dataOutputStream = new DataOutputStream(lz4BlockOutputStream)) { - - byteArrayOutputStream.write(result.getHash()); - - byte[] data = result.getData(); - dataOutputStream.writeInt(data.length); - dataOutputStream.write(data, 0, data.length); - - Collection proximityBlocks = result.getProximityBlocks(); - dataOutputStream.writeInt(proximityBlocks.size()); - for (BlockPos blockPosition : proximityBlocks) { - dataOutputStream.writeInt(blockPosition.toSectionPos()); - } - - Collection removedEntities = result.getBlockEntities(); - dataOutputStream.writeInt(removedEntities.size()); - for (BlockPos blockPosition : removedEntities) { - dataOutputStream.writeInt(blockPosition.toSectionPos()); - } - } catch (Exception e) { - new IOException("Unable to compress chunk: " + result.getCacheKey(), e).printStackTrace(); - return null; - } - - return new CacheChunkEntry(result.getCacheKey(), byteArrayOutputStream.toByteArray()); - } - - private final ChunkCacheKey key; - private final byte[] compressedData; - - public CacheChunkEntry(ChunkCacheKey key, byte[] data) { - this.key = key; - this.compressedData = data; - } - - public byte[] compressedData() { - return compressedData; - } - - public int estimatedSize() { - return 128 + this.compressedData.length; - } - - public boolean isValid(ObfuscationRequest request) { - try { - return request != null && Arrays.equals(this.compressedData, 0, ObfuscationRequest.HASH_LENGTH, - request.getChunkHash(), 0, ObfuscationRequest.HASH_LENGTH); - } catch (Exception e) { - throw new RuntimeException("unable to validate", e); - } - } - - public Optional toResult() { - try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.compressedData); - LZ4BlockInputStream lz4BlockInputStream = new LZ4BlockInputStream(byteArrayInputStream); - DataInputStream dataInputStream = new DataInputStream(lz4BlockInputStream)) { - - byte[] hash = Arrays.copyOf(this.compressedData, ObfuscationRequest.HASH_LENGTH); - byteArrayInputStream.skip(ObfuscationRequest.HASH_LENGTH); - - byte[] data = new byte[dataInputStream.readInt()]; - dataInputStream.readFully(data); - - ObfuscationResult result = new ObfuscationResult(this.key, hash, data); - - int x = this.key.x() << 4; - int z = this.key.z() << 4; - - Collection proximityBlocks = result.getProximityBlocks(); - for (int i = dataInputStream.readInt(); i > 0; i--) { - proximityBlocks.add(BlockPos.fromSectionPos(x, z, dataInputStream.readInt())); - } - - Collection removedEntities = result.getBlockEntities(); - for (int i = dataInputStream.readInt(); i > 0; i--) { - removedEntities.add(BlockPos.fromSectionPos(x, z, dataInputStream.readInt())); - } - - return Optional.of(result); - } catch (Exception e) { - new IOException("Unable to decompress chunk: " + this.key, e).printStackTrace(); - return Optional.empty(); - } - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/ChunkSerializer.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/ChunkSerializer.java deleted file mode 100644 index 3e8c77ff..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/ChunkSerializer.java +++ /dev/null @@ -1,62 +0,0 @@ -package net.imprex.orebfuscator.cache; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.util.ChunkCacheKey; - -public class ChunkSerializer { - - private static final int CACHE_VERSION = 2; - - private final AbstractRegionFileCache regionFileCache; - - public ChunkSerializer(AbstractRegionFileCache regionFileCache) { - this.regionFileCache = regionFileCache; - } - - @Nullable - public CacheChunkEntry read(@NotNull ChunkCacheKey key) throws IOException { - try (DataInputStream dataInputStream = this.regionFileCache.createInputStream(key)) { - if (dataInputStream != null) { - // check if cache entry has right version and if chunk is present - if (dataInputStream.readInt() != CACHE_VERSION || !dataInputStream.readBoolean()) { - return null; - } - - byte[] compressedData = new byte[dataInputStream.readInt()]; - dataInputStream.readFully(compressedData); - - return new CacheChunkEntry(key, compressedData); - } - } catch (IOException e) { - throw new IOException("Unable to read chunk: " + key, e); - } - - return null; - } - - public void write(@NotNull ChunkCacheKey key, @Nullable CacheChunkEntry value) throws IOException { - try (DataOutputStream dataOutputStream = this.regionFileCache.createOutputStream(key)) { - dataOutputStream.writeInt(CACHE_VERSION); - - if (value != null) { - dataOutputStream.writeBoolean(true); - - byte[] compressedData = value.compressedData(); - dataOutputStream.writeInt(compressedData.length); - dataOutputStream.write(compressedData); - } else { - dataOutputStream.writeBoolean(false); - } - } catch (IOException e) { - throw new IOException("Unable to write chunk: " + key, e); - } - } - -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/ObfuscationCache.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/ObfuscationCache.java deleted file mode 100644 index c88d9a2e..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/cache/ObfuscationCache.java +++ /dev/null @@ -1,142 +0,0 @@ -package net.imprex.orebfuscator.cache; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import org.jetbrains.annotations.NotNull; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.RemovalNotification; - -import dev.imprex.orebfuscator.cache.AbstractRegionFileCache; -import dev.imprex.orebfuscator.config.api.CacheConfig; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.imprex.orebfuscator.Orebfuscator; -import net.imprex.orebfuscator.OrebfuscatorCompatibility; -import net.imprex.orebfuscator.OrebfuscatorNms; -import net.imprex.orebfuscator.OrebfuscatorStatistics; -import net.imprex.orebfuscator.obfuscation.ObfuscationRequest; -import net.imprex.orebfuscator.obfuscation.ObfuscationResult; - -public class ObfuscationCache { - - private final CacheConfig cacheConfig; - private final OrebfuscatorStatistics statistics; - - private final AbstractRegionFileCache regionFileCache; - private final Cache cache; - private final AsyncChunkSerializer serializer; - - public ObfuscationCache(Orebfuscator orebfuscator) { - this.cacheConfig = orebfuscator.getOrebfuscatorConfig().cache(); - this.statistics = orebfuscator.getStatistics(); - - this.cache = CacheBuilder.newBuilder() - .maximumSize(this.cacheConfig.maximumSize()) - .expireAfterAccess(this.cacheConfig.expireAfterAccess(), TimeUnit.MILLISECONDS) - .removalListener(this::onRemoval) - .build(); - this.statistics.setMemoryCacheSizeSupplier(() -> this.cache.size()); - - this.regionFileCache = OrebfuscatorNms.createRegionFileCache(orebfuscator.getOrebfuscatorConfig()); - - if (this.cacheConfig.enableDiskCache()) { - this.serializer = new AsyncChunkSerializer(orebfuscator, regionFileCache); - } else { - this.serializer = null; - } - - if (this.cacheConfig.enabled() && this.cacheConfig.deleteRegionFilesAfterAccess() > 0) { - OrebfuscatorCompatibility.runAsyncAtFixedRate(new CacheFileCleanupTask(orebfuscator, regionFileCache), 0, 72000L); - } - } - - private void onRemoval(@NotNull RemovalNotification notification) { - this.statistics.onCacheSizeChange(-notification.getValue().estimatedSize()); - - // don't serialize invalidated chunks since this would require locking the main - // thread and wouldn't bring a huge improvement - if (this.cacheConfig.enableDiskCache() && notification.wasEvicted() && !OrebfuscatorCompatibility.isGameThread()) { - this.serializer.write(notification.getKey(), notification.getValue()); - } - } - - private void requestObfuscation(@NotNull ObfuscationRequest request) { - request.submitForObfuscation().thenAccept(chunk -> { - var compressedChunk = CacheChunkEntry.create(chunk); - if (compressedChunk != null) { - this.cache.put(request.getCacheKey(), compressedChunk); - this.statistics.onCacheSizeChange(compressedChunk.estimatedSize()); - } - }); - } - - @NotNull - public CompletableFuture get(@NotNull ObfuscationRequest request) { - ChunkCacheKey key = request.getCacheKey(); - - CacheChunkEntry cacheChunk = this.cache.getIfPresent(key); - if (cacheChunk != null && cacheChunk.isValid(request)) { - this.statistics.onCacheHitMemory(); - - // complete request - cacheChunk.toResult().ifPresentOrElse(request::complete, - // request obfuscation if decoding failed - () -> this.requestObfuscation(request)); - } - // only access disk cache if we couldn't find the chunk in memory cache - else if (cacheChunk == null && this.cacheConfig.enableDiskCache()) { - this.serializer.read(key).whenComplete((diskChunk, throwable) -> { - if (diskChunk != null && diskChunk.isValid(request)) { - this.statistics.onCacheHitDisk(); - - // add valid disk cache entry to in-memory cache - this.cache.put(key, diskChunk); - this.statistics.onCacheSizeChange(diskChunk.estimatedSize()); - - // complete request - diskChunk.toResult().ifPresentOrElse(request::complete, - // request obfuscation if decoding failed - () -> this.requestObfuscation(request)); - } else { - this.statistics.onCacheMiss(); - - // request obfuscation if disk cache missing - this.requestObfuscation(request); - } - - // request future doesn't care about serialzer failure because - // we simply request obfuscation on failure - if (throwable != null) { - throwable.printStackTrace(); - } - }); - } - // request obfuscation if cache missed - else { - this.statistics.onCacheMiss(); - this.requestObfuscation(request); - } - - return request.getFuture(); - } - - public void invalidate(ChunkCacheKey key) { - this.cache.invalidate(key); - } - - public void close() { - if (this.serializer != null) { - // flush memory cache to disk on shutdown - this.cache.asMap().entrySet().removeIf(entry -> { - this.serializer.write(entry.getKey(), entry.getValue()); - return true; - }); - - this.serializer.close(); - } - - this.regionFileCache.clear(); - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitChunkPacketAccessor.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitChunkPacketAccessor.java index 7607703e..007d21f0 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitChunkPacketAccessor.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitChunkPacketAccessor.java @@ -1,29 +1,27 @@ package net.imprex.orebfuscator.iterop; -import java.util.BitSet; -import java.util.Iterator; -import java.util.List; -import java.util.function.Predicate; - import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtCompound; - import dev.imprex.orebfuscator.interop.ChunkPacketAccessor; -import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.obfuscation.ObfuscationResponse; import dev.imprex.orebfuscator.util.BlockPos; +import java.util.BitSet; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; import net.imprex.orebfuscator.util.MinecraftVersion; import net.imprex.orebfuscator.util.WrappedClientboundLevelChunkPacketData; +import org.jspecify.annotations.NullMarked; +@NullMarked public class BukkitChunkPacketAccessor implements ChunkPacketAccessor { private static final boolean HAS_CLIENTBOUND_LEVEL_CHUNK_PACKET_DATA = MinecraftVersion.isAtOrAbove("1.18"); private static final boolean HAS_HEIGHT_BITMASK = MinecraftVersion.isBelow("1.18"); private static final boolean HAS_VARINT_BITMASK = MinecraftVersion.isBelow("1.17"); - public final BukkitWorldAccessor worldAccessor; - private final int chunkX; private final int chunkZ; @@ -35,7 +33,6 @@ public class BukkitChunkPacketAccessor implements ChunkPacketAccessor { public BukkitChunkPacketAccessor(PacketContainer packet, BukkitWorldAccessor worldAccessor) { this.packet = packet; - this.worldAccessor = worldAccessor; StructureModifier packetInteger = packet.getIntegers(); this.chunkX = packetInteger.read(0); @@ -57,15 +54,10 @@ public BukkitChunkPacketAccessor(PacketContainer packet, BukkitWorldAccessor wor } } else { this.sectionMask = new BitSet(); - this.sectionMask.set(0, worldAccessor.getSectionCount()); + this.sectionMask.set(0, worldAccessor.sectionCount()); } } - @Override - public WorldAccessor world() { - return this.worldAccessor; - } - @Override public int chunkX() { return this.chunkX; @@ -87,26 +79,21 @@ public byte[] data() { } @Override - public void setData(byte[] data) { - if (this.packetData != null) { - this.packetData.setBuffer(data); - } else { - this.packet.getByteArrays().write(0, data); - } - } + public void update(ObfuscationResponse response) { + Predicate blockEntityPredicate = relativePostion -> + response.blockEntities().contains(relativePostion.add(chunkX << 4, 0, chunkZ << 4)); - @Override - public void filterBlockEntities(Predicate predicate) { if (this.packetData != null) { - this.packetData.removeBlockEntityIf(relativePostion -> - predicate.test(relativePostion.add(chunkX << 4, 0, chunkZ << 4))); + this.packetData.setBuffer(response.data()); + this.packetData.removeBlockEntityIf(blockEntityPredicate); } else { - removeTileEntitiesFromPacket(this.packet, predicate); + this.packet.getByteArrays().write(0, response.data()); + removeTileEntitiesFromPacket(blockEntityPredicate); } } - private void removeTileEntitiesFromPacket(PacketContainer packet, Predicate predicate) { - StructureModifier>> packetNbtList = packet.getListNbtModifier(); + private void removeTileEntitiesFromPacket(Predicate predicate) { + StructureModifier>> packetNbtList = this.packet.getListNbtModifier(); List> tileEntities = packetNbtList.read(0); this.removeTileEntities(tileEntities, predicate); diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitLoggerAccessor.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitLoggerAccessor.java index 872cf1c0..74400da5 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitLoggerAccessor.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitLoggerAccessor.java @@ -1,28 +1,26 @@ package net.imprex.orebfuscator.iterop; -import java.util.Objects; -import java.util.logging.Logger; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - import dev.imprex.orebfuscator.logging.LogLevel; import dev.imprex.orebfuscator.logging.LoggerAccessor; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -public class BukkitLoggerAccessor implements LoggerAccessor { - - private final Logger logger; +@NullMarked +public record BukkitLoggerAccessor(Logger logger) implements LoggerAccessor { - public BukkitLoggerAccessor(@NotNull Logger logger) { - this.logger = Objects.requireNonNull(logger, "Plugin logger can't be null"); + public BukkitLoggerAccessor { + Objects.requireNonNull(logger, "Plugin logger can't be null"); } @Override - public void log(@NotNull LogLevel level, @NotNull String message, @Nullable Throwable throwable) { + public void log(LogLevel level, String message, @Nullable Throwable throwable) { var mappedLevel = switch (level) { - case DEBUG, INFO -> java.util.logging.Level.INFO; - case WARN -> java.util.logging.Level.WARNING; - case ERROR -> java.util.logging.Level.SEVERE; + case DEBUG, INFO -> Level.INFO; + case WARN -> Level.WARNING; + case ERROR -> Level.SEVERE; }; if (level == LogLevel.DEBUG) { diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitPlayerAccessor.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitPlayerAccessor.java new file mode 100644 index 00000000..3350317f --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitPlayerAccessor.java @@ -0,0 +1,137 @@ +package net.imprex.orebfuscator.iterop; + +import com.comphenix.protocol.AsynchronousManager; +import com.comphenix.protocol.events.PacketEvent; +import dev.imprex.orebfuscator.PermissionRequirements; +import dev.imprex.orebfuscator.interop.PlayerAccessor; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.player.OrebfuscatorPlayer; +import dev.imprex.orebfuscator.util.BlockPos; +import dev.imprex.orebfuscator.util.EntityPose; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import net.imprex.orebfuscator.Orebfuscator; +import net.imprex.orebfuscator.OrebfuscatorCompatibility; +import net.imprex.orebfuscator.OrebfuscatorNms; +import net.imprex.orebfuscator.obfuscation.PendingChunkBatch; +import net.imprex.orebfuscator.util.PermissionUtil; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public class BukkitPlayerAccessor implements PlayerAccessor { + + private final Orebfuscator orebfuscator; + private final Player player; + private BukkitWorldAccessor world; + + private final OrebfuscatorPlayer orebfuscatorPlayer; + + private final Map> pendingPackets = new WeakHashMap<>(); + private final AtomicReference<@Nullable PendingChunkBatch> chunkBatch = new AtomicReference<>(); + + public BukkitPlayerAccessor(Orebfuscator orebfuscator, Player player) { + this.orebfuscator = orebfuscator; + this.player = player; + this.world = orebfuscator.worldManager().get(player.getWorld()); + this.orebfuscatorPlayer = new OrebfuscatorPlayer(orebfuscator, this); + } + + public void changeWorld(BukkitWorldAccessor world) { + this.world = world; + this.orebfuscatorPlayer.clearChunks(); + } + + public @Nullable CompletableFuture obfuscationFuture(PacketEvent event) { + return pendingPackets.remove(event.getPacket().getHandle()); + } + + public void obfuscationFuture(PacketEvent event, CompletableFuture future) { + pendingPackets.putIfAbsent(event.getPacket().getHandle(), future); + } + + public void startBatch(AsynchronousManager asynchronousManager, PacketEvent event) { + var nextBatch = new PendingChunkBatch(this.orebfuscator.statistics().injector, asynchronousManager, event); + var prevBatch = this.chunkBatch.getAndSet(nextBatch); + + if (prevBatch != null) { + prevBatch.finish(); + OfcLogger.warn("Pending chunk batch discarded because a new batch was initiated."); + } + } + + public boolean addBatchChunk(CompletableFuture future) { + var batch = this.chunkBatch.get(); + if (batch != null) { + batch.addChunk(future); + return true; + } + return false; + } + + public void finishBatch() { + var batch = this.chunkBatch.getAndSet(null); + if (batch != null) { + batch.finish(); + } + } + + @Override + public OrebfuscatorPlayer orebfuscatorPlayer() { + return this.orebfuscatorPlayer; + } + + @Override + public EntityPose pose() { + var location = player.getLocation(); + return new EntityPose(world, location.getX(), location.getY(), location.getZ(), location.getPitch(), + location.getYaw()); + } + + @Override + public EntityPose eyePose() { + var location = player.getEyeLocation(); + return new EntityPose(world, location.getX(), location.getY(), location.getZ(), location.getPitch(), + location.getYaw()); + } + + @Override + public BukkitWorldAccessor world() { + return world; + } + + @Override + public boolean isAlive() { + return !player.isDead(); + } + + @Override + public boolean isSpectator() { + return player.getGameMode() == GameMode.SPECTATOR; + } + + @Override + public double lavaFogDistance() { + return player.hasPotionEffect(PotionEffectType.FIRE_RESISTANCE) ? 7 : 2; + } + + @Override + public boolean hasPermission(PermissionRequirements permission) { + return PermissionUtil.hasPermission(player, permission); + } + + @Override + public void runForPlayer(Runnable runnable) { + OrebfuscatorCompatibility.runForPlayer(player, runnable); + } + + @Override + public void sendBlockUpdates(Iterable iterable) { + OrebfuscatorNms.sendBlockUpdates(player, iterable); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitPlayerAccessorManager.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitPlayerAccessorManager.java new file mode 100644 index 00000000..e91685b0 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitPlayerAccessorManager.java @@ -0,0 +1,75 @@ +package net.imprex.orebfuscator.iterop; + +import dev.imprex.orebfuscator.interop.PlayerAccessor; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.imprex.orebfuscator.Orebfuscator; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public class BukkitPlayerAccessorManager implements Listener { + + private final Map players = new HashMap<>(); + + private final Orebfuscator orebfuscator; + private final BukkitWorldAccessorManager worldManager; + + public BukkitPlayerAccessorManager(Orebfuscator orebfuscator) { + this.orebfuscator = orebfuscator; + this.worldManager = orebfuscator.worldManager(); + + Bukkit.getPluginManager().registerEvents(this, orebfuscator); + + for (Player player : Bukkit.getOnlinePlayers()) { + this.players.computeIfAbsent(player, key -> new BukkitPlayerAccessor(orebfuscator, key)) + .orebfuscatorPlayer().clearChunks(); + } + } + + public List all() { + return this.players.values().stream() + .map(PlayerAccessor.class::cast) + .toList(); + } + + public @Nullable BukkitPlayerAccessor tryGet(Player player) { + return this.players.get(player); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + this.players.computeIfAbsent(event.getPlayer(), key -> new BukkitPlayerAccessor(orebfuscator, key)) + .orebfuscatorPlayer().clearChunks(); + } + + @EventHandler + public void onPlayerChangedWorld(PlayerChangedWorldEvent event) { + var player = event.getPlayer(); + var bukkitPlayer = this.players.get(player); + if (bukkitPlayer != null) { + bukkitPlayer.changeWorld(this.worldManager.get(player.getWorld())); + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + this.players.remove(event.getPlayer()); + } + + @EventHandler + public void onDisable(PluginDisableEvent event) { + if (event.getPlugin() == this.orebfuscator) { + this.players.clear(); + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitWorldAccessor.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitWorldAccessor.java index d89fce1e..1527cfa8 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitWorldAccessor.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitWorldAccessor.java @@ -1,42 +1,34 @@ package net.imprex.orebfuscator.iterop; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldLoadEvent; -import org.bukkit.event.world.WorldUnloadEvent; -import org.bukkit.plugin.Plugin; - import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.MethodAccessor; - +import dev.imprex.orebfuscator.config.api.WorldConfigBundle; +import dev.imprex.orebfuscator.interop.ChunkAccessor; +import dev.imprex.orebfuscator.interop.ChunkPacketAccessor; import dev.imprex.orebfuscator.interop.WorldAccessor; import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.obfuscation.ObfuscationRequest; +import dev.imprex.orebfuscator.util.BlockPos; +import dev.imprex.orebfuscator.util.ChunkDirection; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import net.imprex.orebfuscator.Orebfuscator; +import net.imprex.orebfuscator.OrebfuscatorNms; import net.imprex.orebfuscator.util.MinecraftVersion; +import org.bukkit.World; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class BukkitWorldAccessor implements WorldAccessor { private static final boolean HAS_DYNAMIC_HEIGHT = MinecraftVersion.isAtOrAbove("1.17"); - private static final Map ACCESSOR_LOOKUP = new ConcurrentHashMap<>(); - - public static BukkitWorldAccessor get(World world) { - return ACCESSOR_LOOKUP.computeIfAbsent(world, key -> { - OfcLogger.warn("Created world accessor outside of event!"); - return new BukkitWorldAccessor(key); - }); - } - - private static final MethodAccessor WORLD_GET_MAX_HEIGHT = getWorldMethod("getMaxHeight"); - private static final MethodAccessor WORLD_GET_MIN_HEIGHT = getWorldMethod("getMinHeight"); + private static final @Nullable MethodAccessor WORLD_GET_MAX_HEIGHT = getWorldMethod("getMaxHeight"); + private static final @Nullable MethodAccessor WORLD_GET_MIN_HEIGHT = getWorldMethod("getMinHeight"); - private static MethodAccessor getWorldMethod(String methodName) { + private static @Nullable MethodAccessor getWorldMethod(String methodName) { if (HAS_DYNAMIC_HEIGHT) { MethodAccessor methodAccessor = getWorldMethod0(World.class, methodName); if (methodAccessor == null) { @@ -48,7 +40,7 @@ private static MethodAccessor getWorldMethod(String methodName) { return null; } - private static MethodAccessor getWorldMethod0(Class target, String methodName) { + private static @Nullable MethodAccessor getWorldMethod0(Class target, String methodName) { try { return Accessors.getMethodAccessor(target, methodName); } catch (IllegalArgumentException e) { @@ -66,38 +58,19 @@ private static int blockToSectionCoord(int block) { return block >> 4; } - public static Collection getWorlds() { - return ACCESSOR_LOOKUP.values(); - } - - public static void registerListener(Plugin plugin) { - for (World world : Bukkit.getWorlds()) { - ACCESSOR_LOOKUP.put(world, new BukkitWorldAccessor(world)); - } - - Bukkit.getPluginManager().registerEvents(new Listener() { - @EventHandler - public void onWorldUnload(WorldLoadEvent event) { - World world = event.getWorld(); - ACCESSOR_LOOKUP.put(world, new BukkitWorldAccessor(world)); - } - - @EventHandler - public void onWorldUnload(WorldUnloadEvent event) { - ACCESSOR_LOOKUP.remove(event.getWorld()); - } - }, plugin); - } - public final World world; + private final Orebfuscator orebfuscator; private final int maxHeight; private final int minHeight; - private BukkitWorldAccessor(World world) { + private @Nullable WorldConfigBundle worldConfigBundle; + + BukkitWorldAccessor(World world, Orebfuscator orebfuscator) { this.world = Objects.requireNonNull(world); + this.orebfuscator = Objects.requireNonNull(orebfuscator); - if (HAS_DYNAMIC_HEIGHT) { + if (WORLD_GET_MAX_HEIGHT != null && WORLD_GET_MIN_HEIGHT != null) { this.maxHeight = (int) WORLD_GET_MAX_HEIGHT.invoke(world); this.minHeight = (int) WORLD_GET_MIN_HEIGHT.invoke(world); } else { @@ -107,43 +80,104 @@ private BukkitWorldAccessor(World world) { } @Override - public String getName() { + public WorldConfigBundle config() { + if (this.worldConfigBundle == null) { + this.worldConfigBundle = this.orebfuscator.config().world(this); + } + return this.worldConfigBundle; + } + + @Override + public String name() { return this.world.getName(); } @Override - public int getHeight() { + public int height() { return this.maxHeight - this.minHeight; } @Override - public int getMinBuildHeight() { + public int minBuildHeight() { return this.minHeight; } @Override - public int getMaxBuildHeight() { + public int maxBuildHeight() { return this.maxHeight; } @Override - public int getSectionCount() { - return this.getMaxSection() - this.getMinSection(); + public int sectionCount() { + return this.maxSection() - this.minSection(); + } + + @Override + public int minSection() { + return blockToSectionCoord(this.minBuildHeight()); + } + + @Override + public int maxSection() { + return blockToSectionCoord(this.maxBuildHeight() - 1) + 1; } @Override - public int getMinSection() { - return blockToSectionCoord(this.getMinBuildHeight()); + public int sectionIndex(int y) { + return blockToSectionCoord(y) - minSection(); + } + + public ChunkAccessor[] getNeighboringChunksNow(int chunkX, int chunkZ) { + final ChunkAccessor[] chunks = new ChunkAccessor[4]; + + for (ChunkDirection direction : ChunkDirection.values()) { + int x = chunkX + direction.getOffsetX(); + int z = chunkZ + direction.getOffsetZ(); + chunks[direction.ordinal()] = getChunkNow(x, z); + } + + return chunks; + } + + @Override + @SuppressWarnings("unchecked") + public CompletableFuture getNeighboringChunks(ObfuscationRequest request) { + var neighborChunks = request.neighborChunks(); + if (neighborChunks != null && Arrays.stream(neighborChunks).noneMatch(ChunkAccessor::isNullOrEmpty)) { + return CompletableFuture.completedFuture(neighborChunks); + } + + final ChunkPacketAccessor packet = request.packet(); + final CompletableFuture[] futures = (CompletableFuture[]) new CompletableFuture[4]; + + for (ChunkDirection direction : ChunkDirection.values()) { + int chunkX = packet.chunkX() + direction.getOffsetX(); + int chunkZ = packet.chunkZ() + direction.getOffsetZ(); + + int index = direction.ordinal(); + var chunk = neighborChunks != null ? neighborChunks[index] : null; + + if (ChunkAccessor.isNullOrEmpty(chunk)) { + futures[index] = OrebfuscatorNms.getChunkFuture(world, chunkX, chunkZ) + .thenApply(ChunkAccessor::ofNullable); + } else { + futures[index] = CompletableFuture.completedFuture(chunk); + } + } + + return CompletableFuture.allOf(futures).thenApply(v -> + Arrays.stream(futures).map(CompletableFuture::join).toArray(ChunkAccessor[]::new)); } @Override - public int getMaxSection() { - return blockToSectionCoord(this.getMaxBuildHeight() - 1) + 1; + public ChunkAccessor getChunkNow(int chunkX, int chunkZ) { + ChunkAccessor chunkAccessor = OrebfuscatorNms.getChunkNow(world, chunkX, chunkZ); + return ChunkAccessor.ofNullable(chunkAccessor); } @Override - public int getSectionIndex(int y) { - return blockToSectionCoord(y) - getMinSection(); + public void sendBlockUpdates(Iterable iterable) { + OrebfuscatorNms.sendBlockUpdates(world, iterable); } @Override @@ -161,6 +195,6 @@ public boolean equals(Object obj) { @Override public String toString() { - return String.format("[%s, minY=%s, maxY=%s]", world.getName(), minHeight, maxHeight); + return "[name=%s, minY=%s, maxY=%s]".formatted(name(), minHeight, maxHeight); } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitWorldAccessorManager.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitWorldAccessorManager.java new file mode 100644 index 00000000..aa5801ff --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/iterop/BukkitWorldAccessorManager.java @@ -0,0 +1,63 @@ +package net.imprex.orebfuscator.iterop; + +import dev.imprex.orebfuscator.interop.WorldAccessor; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.imprex.orebfuscator.Orebfuscator; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldUnloadEvent; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class BukkitWorldAccessorManager implements Listener { + + private final Map worlds = new HashMap<>(); + + private final Orebfuscator orebfuscator; + + public BukkitWorldAccessorManager(Orebfuscator orebfuscator) { + this.orebfuscator = orebfuscator; + Bukkit.getPluginManager().registerEvents(this, orebfuscator); + + for (World world : Bukkit.getWorlds()) { + this.worlds.put(world, new BukkitWorldAccessor(world, orebfuscator)); + } + } + + public List all() { + return this.worlds.values().stream() + .map(WorldAccessor.class::cast) + .toList(); + } + + public BukkitWorldAccessor get(World world) { + var bukkitWorld = this.worlds.get(world); + if (bukkitWorld == null) { + throw new IllegalStateException("Can't find accessor for world " + world.getName()); + } + return bukkitWorld; + } + + @EventHandler + public void onWorldLoad(WorldLoadEvent event) { + this.worlds.put(event.getWorld(), new BukkitWorldAccessor(event.getWorld(), this.orebfuscator)); + } + + @EventHandler + public void onWorldUnload(WorldUnloadEvent event) { + this.worlds.remove(event.getWorld()); + } + + @EventHandler + public void onDisable(PluginDisableEvent event) { + if (event.getPlugin() == this.orebfuscator) { + this.worlds.clear(); + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/DeobfuscationListener.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/DeobfuscationListener.java index c8a0f08b..f691181f 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/DeobfuscationListener.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/DeobfuscationListener.java @@ -1,5 +1,17 @@ package net.imprex.orebfuscator.obfuscation; +import dev.imprex.orebfuscator.PermissionRequirements; +import dev.imprex.orebfuscator.UpdateSystem; +import dev.imprex.orebfuscator.config.OrebfuscatorConfig; +import dev.imprex.orebfuscator.util.ConsoleUtil; +import net.imprex.orebfuscator.Orebfuscator; +import net.imprex.orebfuscator.OrebfuscatorCompatibility; +import net.imprex.orebfuscator.util.PermissionUtil; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.hover.content.Text; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -18,70 +30,66 @@ import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.jspecify.annotations.NullMarked; -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; -import net.imprex.orebfuscator.Orebfuscator; -import net.imprex.orebfuscator.UpdateSystem; -import net.imprex.orebfuscator.util.ConsoleUtil; -import net.imprex.orebfuscator.util.PermissionUtil; - +@NullMarked public class DeobfuscationListener implements Listener { - public static void createAndRegister(Orebfuscator orebfuscator, DeobfuscationWorker deobfuscationWorker) { - Listener listener = new DeobfuscationListener(orebfuscator, deobfuscationWorker); + public static void createAndRegister(Orebfuscator orebfuscator, ObfuscationSystem obfuscationSystem) { + Listener listener = new DeobfuscationListener(orebfuscator, obfuscationSystem); Bukkit.getPluginManager().registerEvents(listener, orebfuscator); } private final UpdateSystem updateSystem; private final OrebfuscatorConfig config; - private final DeobfuscationWorker deobfuscationWorker; + private final ObfuscationSystem obfuscationSystem; - private DeobfuscationListener(Orebfuscator orebfuscator, DeobfuscationWorker deobfuscationWorker) { - this.updateSystem = orebfuscator.getUpdateSystem(); - this.config = orebfuscator.getOrebfuscatorConfig(); - this.deobfuscationWorker = deobfuscationWorker; + private DeobfuscationListener(Orebfuscator orebfuscator, ObfuscationSystem obfuscationSystem) { + this.updateSystem = orebfuscator.updateSystem(); + this.config = orebfuscator.config(); + this.obfuscationSystem = obfuscationSystem; } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockDamage(BlockDamageEvent event) { if (this.config.general().updateOnBlockDamage()) { - this.deobfuscationWorker.deobfuscate(event.getBlock()); + this.obfuscationSystem.deobfuscate(event.getBlock()); } } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockBreak(BlockBreakEvent event) { - this.deobfuscationWorker.deobfuscate(event.getBlock()); + this.obfuscationSystem.deobfuscate(event.getBlock()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockBurn(BlockBurnEvent event) { - this.deobfuscationWorker.deobfuscate(event.getBlock()); + this.obfuscationSystem.deobfuscate(event.getBlock()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockExplode(BlockExplodeEvent event) { - this.deobfuscationWorker.deobfuscate(event.blockList(), true); + this.obfuscationSystem.deobfuscate(event.blockList()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockPistonExtend(BlockPistonExtendEvent event) { - this.deobfuscationWorker.deobfuscate(event.getBlocks(), true); + this.obfuscationSystem.deobfuscate(event.getBlocks()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onBlockPistonRetract(BlockPistonRetractEvent event) { - this.deobfuscationWorker.deobfuscate(event.getBlocks(), true); + this.obfuscationSystem.deobfuscate(event.getBlocks()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityExplode(EntityExplodeEvent event) { - this.deobfuscationWorker.deobfuscate(event.blockList(), true); + this.obfuscationSystem.deobfuscate(event.blockList()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onEntityChangeBlock(EntityChangeBlockEvent event) { - this.deobfuscationWorker.deobfuscate(event.getBlock()); + this.obfuscationSystem.deobfuscate(event.getBlock()); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) @@ -90,7 +98,7 @@ public void onPlayerInteract(PlayerInteractEvent event) { && event.getItem() != null && event.getItem().getType() != null) { Material material = event.getItem().getType(); if (material.name().endsWith("_HOE")) { - this.deobfuscationWorker.deobfuscate(event.getClickedBlock()); + this.obfuscationSystem.deobfuscate(event.getClickedBlock()); } } } @@ -99,17 +107,28 @@ public void onPlayerInteract(PlayerInteractEvent event) { public void onJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); - if (this.config.general().bypassNotification() && PermissionUtil.canBypassObfuscate(player)) { + if (this.config.general().bypassNotification() && PermissionUtil.hasPermission(player, + PermissionRequirements.BYPASS)) { player.sendMessage( "[§bOrebfuscator§f]§7 You bypass Orebfuscator because you have the 'orebfuscator.bypass' permission."); } - if (PermissionUtil.canAccessAdminTools(player)) { + if (PermissionUtil.hasPermission(player, PermissionRequirements.ADMIN)) { String configReport = this.config.report(); if (configReport != null) { player.sendMessage("[§bOrebfuscator§f]§c " + ConsoleUtil.replaceAnsiColorWithChatColor(configReport)); } - this.updateSystem.checkForUpdates(player); + + this.updateSystem.ifNewerDownloadAvailable(downloadUri -> { + BaseComponent[] components = new ComponentBuilder("[§bOrebfuscator§f]§7 A new release is available ") + .append("§f§l[CLICK HERE]") + .event(new ClickEvent(ClickEvent.Action.OPEN_URL, downloadUri)) + .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new Text(new ComponentBuilder("§7Click here to see the latest release").create()))).create(); + OrebfuscatorCompatibility.runForPlayer(player, () -> { + player.spigot().sendMessage(components); + }); + }); } } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/DeobfuscationWorker.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/DeobfuscationWorker.java deleted file mode 100644 index 143333ce..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/DeobfuscationWorker.java +++ /dev/null @@ -1,110 +0,0 @@ -package net.imprex.orebfuscator.obfuscation; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import org.bukkit.World; -import org.bukkit.block.Block; - -import com.google.common.collect.Iterables; - -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; -import dev.imprex.orebfuscator.config.api.BlockFlags; -import dev.imprex.orebfuscator.config.api.ObfuscationConfig; -import dev.imprex.orebfuscator.config.api.WorldConfigBundle; -import dev.imprex.orebfuscator.util.BlockPos; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.imprex.orebfuscator.Orebfuscator; -import net.imprex.orebfuscator.OrebfuscatorNms; -import net.imprex.orebfuscator.cache.ObfuscationCache; -import net.imprex.orebfuscator.iterop.BukkitWorldAccessor; - -public class DeobfuscationWorker { - - private final OrebfuscatorConfig config; - private final ObfuscationCache cache; - - public DeobfuscationWorker(Orebfuscator orebfuscator) { - this.config = orebfuscator.getOrebfuscatorConfig(); - this.cache = orebfuscator.getObfuscationCache(); - } - - void deobfuscate(Block block) { - if (block == null || !block.getType().isOccluding()) { - return; - } - - deobfuscate(Arrays.asList(block), true); - } - - public void deobfuscate(Collection blocks, boolean occluding) { - if (blocks.isEmpty()) { - return; - } - - World world = Iterables.get(blocks, 0).getWorld(); - BukkitWorldAccessor worldAccessor = BukkitWorldAccessor.get(world); - WorldConfigBundle bundle = this.config.world(worldAccessor); - - ObfuscationConfig obfuscationConfig = bundle.obfuscation(); - if (obfuscationConfig == null || !obfuscationConfig.isEnabled()) { - return; - } - - int updateRadius = this.config.general().updateRadius(); - BlockFlags blockFlags = bundle.blockFlags(); - - try (Processor processor = new Processor(worldAccessor, blockFlags)) { - for (Block block : blocks) { - if (!occluding || block.getType().isOccluding()) { - BlockPos position = new BlockPos(block.getX(), block.getY(), block.getZ()); - processor.processPosition(position, updateRadius); - } - } - } - } - - public class Processor implements AutoCloseable { - - private final Set updatedBlocks = new HashSet<>(); - private final Set invalidChunks = new HashSet<>(); - - private final BukkitWorldAccessor worldAccessor; - private final BlockFlags blockFlags; - - public Processor(BukkitWorldAccessor worldAccessor, BlockFlags blockFlags) { - this.worldAccessor = worldAccessor; - this.blockFlags = blockFlags; - } - - public void processPosition(BlockPos position, int depth) { - int blockId = OrebfuscatorNms.getBlockState(worldAccessor.world, position); - if (BlockFlags.isObfuscateBitSet(blockFlags.flags(blockId)) && updatedBlocks.add(position)) { - - // invalidate cache if enabled - if (config.cache().enabled()) { - ChunkCacheKey chunkPosition = new ChunkCacheKey(worldAccessor, position); - if (this.invalidChunks.add(chunkPosition)) { - cache.invalidate(chunkPosition); - } - } - } - - if (depth-- > 0) { - processPosition(position.add(1, 0, 0), depth); - processPosition(position.add(-1, 0, 0), depth); - processPosition(position.add(0, 1, 0), depth); - processPosition(position.add(0, -1, 0), depth); - processPosition(position.add(0, 0, 1), depth); - processPosition(position.add(0, 0, -1), depth); - } - } - - @Override - public void close() { - OrebfuscatorNms.sendBlockUpdates(worldAccessor.world, this.updatedBlocks); - } - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationAsyncListener.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationAsyncListener.java new file mode 100644 index 00000000..9deabef1 --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationAsyncListener.java @@ -0,0 +1,136 @@ +package net.imprex.orebfuscator.obfuscation; + +import com.comphenix.protocol.AsynchronousManager; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.async.AsyncListenerHandler; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketEvent; +import dev.imprex.orebfuscator.PermissionRequirements; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.obfuscation.ObfuscationPipeline; +import dev.imprex.orebfuscator.statistics.InjectorStatistics; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import net.imprex.orebfuscator.Orebfuscator; +import net.imprex.orebfuscator.OrebfuscatorCompatibility; +import net.imprex.orebfuscator.iterop.BukkitChunkPacketAccessor; +import net.imprex.orebfuscator.iterop.BukkitPlayerAccessor; +import net.imprex.orebfuscator.iterop.BukkitPlayerAccessorManager; +import net.imprex.orebfuscator.iterop.BukkitWorldAccessor; +import net.imprex.orebfuscator.util.ServerVersion; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class ObfuscationAsyncListener extends PacketAdapter { + + private static final List PACKET_TYPES = Arrays.asList( + PacketType.Play.Server.MAP_CHUNK, + PacketType.Play.Server.CHUNK_BATCH_START, + PacketType.Play.Server.CHUNK_BATCH_FINISHED, + PacketType.Play.Server.UNLOAD_CHUNK, + PacketType.Play.Server.CHUNKS_BIOMES, + PacketType.Play.Server.LIGHT_UPDATE, + PacketType.Play.Server.TILE_ENTITY_DATA, + PacketType.Play.Server.RESPAWN, + // PlayerList::sendLevelInfo + PacketType.Play.Server.INITIALIZE_BORDER, + PacketType.Play.Server.UPDATE_TIME, + PacketType.Play.Server.SPAWN_POSITION, + PacketType.Play.Server.GAME_STATE_CHANGE, + // Proximity hider updates + PacketType.Play.Server.BLOCK_CHANGE, + PacketType.Play.Server.MULTI_BLOCK_CHANGE, + // Clientbound packet + PacketType.Play.Client.CHUNK_BATCH_RECEIVED); + + private final ObfuscationPipeline pipeline; + private final InjectorStatistics statistics; + private final BukkitPlayerAccessorManager playerManager; + + private final AsynchronousManager asynchronousManager; + private final AsyncListenerHandler asyncListenerHandler; + + public ObfuscationAsyncListener(Orebfuscator orebfuscator) { + super(orebfuscator, PACKET_TYPES.stream() + .filter(PacketType::isSupported) + .collect(Collectors.toList())); + + this.pipeline = orebfuscator.obfuscationPipeline(); + this.statistics = orebfuscator.statistics().injector; + this.playerManager = orebfuscator.playerManager(); + + this.asynchronousManager = ProtocolLibrary.getProtocolManager().getAsynchronousManager(); + this.asyncListenerHandler = this.asynchronousManager.registerAsyncHandler(this); + + if (ServerVersion.isFolia()) { + OrebfuscatorCompatibility.runAsyncNow(this.asyncListenerHandler.getListenerLoop()); + } else { + this.asyncListenerHandler.start(); + } + } + + public void unregister() { + this.asynchronousManager.unregisterAsyncHandler(this.asyncListenerHandler); + } + + @Override + public void onPacketReceiving(PacketEvent event) { + statistics.injectorBatchSize.add(event.getPacket().getFloat().read(0)); + } + + @Override + public void onPacketSending(PacketEvent event) { + PacketType type = event.getPacket().getType(); + if (type != PacketType.Play.Server.MAP_CHUNK && + type != PacketType.Play.Server.CHUNK_BATCH_START && + type != PacketType.Play.Server.CHUNK_BATCH_FINISHED) { + return; + } + + BukkitPlayerAccessor player = this.playerManager.tryGet(event.getPlayer()); + if (player == null || !player.isAlive()) { + return; + } + + BukkitWorldAccessor world = player.world(); + if (player.hasPermission(PermissionRequirements.BYPASS) || !world.config().needsObfuscation()) { + return; + } + + if (type == PacketType.Play.Server.CHUNK_BATCH_START) { + player.startBatch(asynchronousManager, event); + } else if (type == PacketType.Play.Server.CHUNK_BATCH_FINISHED) { + player.finishBatch(); + } else { + var future = player.obfuscationFuture(event); + if (future == null) { + OfcLogger.warn("Processing chunk packet async without an obfuscation future, that shouldn't happen!"); + + var packet = new BukkitChunkPacketAccessor(event.getPacket(), world); + if (packet.isEmpty()) { + future = CompletableFuture.completedFuture(null); + } else { + future = pipeline.request(world, player, packet, null).toCompletableFuture(); + } + } + + if (!player.addBatchChunk(future)) { + // no pending batch so we send each packet individually + event.getAsyncMarker().incrementProcessingDelay(); + + var timer = statistics.packetDelayChunk.start(); + future.whenComplete((result, throwable) -> { + if (throwable != null) { + OfcLogger.error(throwable); + } + + this.asynchronousManager.signalPacketTransmission(event); + timer.stop(); + }); + } + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationListener.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationListener.java deleted file mode 100644 index 959c41ba..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationListener.java +++ /dev/null @@ -1,170 +0,0 @@ -package net.imprex.orebfuscator.obfuscation; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; - -import org.bukkit.entity.Player; - -import com.comphenix.protocol.AsynchronousManager; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.PacketTypeEnum; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.async.AsyncListenerHandler; -import com.comphenix.protocol.events.PacketAdapter; -import com.comphenix.protocol.events.PacketEvent; - -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; -import dev.imprex.orebfuscator.config.api.AdvancedConfig; -import dev.imprex.orebfuscator.logging.OfcLogger; -import dev.imprex.orebfuscator.util.BlockPos; -import net.imprex.orebfuscator.Orebfuscator; -import net.imprex.orebfuscator.OrebfuscatorCompatibility; -import net.imprex.orebfuscator.iterop.BukkitChunkPacketAccessor; -import net.imprex.orebfuscator.iterop.BukkitWorldAccessor; -import net.imprex.orebfuscator.player.OrebfuscatorPlayer; -import net.imprex.orebfuscator.player.OrebfuscatorPlayerMap; -import net.imprex.orebfuscator.util.PermissionUtil; -import net.imprex.orebfuscator.util.RollingAverage; -import net.imprex.orebfuscator.util.ServerVersion; - -public class ObfuscationListener extends PacketAdapter { - - private static final List PACKET_TYPES = Arrays.asList( - PacketType.Play.Server.MAP_CHUNK, - PacketType.Play.Server.UNLOAD_CHUNK, - PacketType.Play.Server.LIGHT_UPDATE, - PacketType.Play.Server.TILE_ENTITY_DATA, - tryGetPacketType(PacketType.Play.Client.getInstance(), "CHUNK_BATCH_RECEIVED") - ); - - private static PacketType tryGetPacketType(PacketTypeEnum packetTypeEnum, String name) { - return packetTypeEnum.values().stream() - .filter(packetType -> packetType.name().equals(name)) - .findAny() - .orElse(null); - } - - private final OrebfuscatorConfig config; - private final OrebfuscatorPlayerMap playerMap; - private final ObfuscationSystem obfuscationSystem; - - private final AsynchronousManager asynchronousManager; - private final AsyncListenerHandler asyncListenerHandler; - - private final RollingAverage originalSize = new RollingAverage(2048); - private final RollingAverage obfuscatedSize = new RollingAverage(2048); - - public ObfuscationListener(Orebfuscator orebfuscator) { - super(orebfuscator, PACKET_TYPES.stream() - .filter(Objects::nonNull) - .filter(PacketType::isSupported) - .collect(Collectors.toList())); - - this.config = orebfuscator.getOrebfuscatorConfig(); - this.playerMap = orebfuscator.getPlayerMap(); - this.obfuscationSystem = orebfuscator.getObfuscationSystem(); - - this.asynchronousManager = ProtocolLibrary.getProtocolManager().getAsynchronousManager(); - this.asyncListenerHandler = this.asynchronousManager.registerAsyncHandler(this); - - if (ServerVersion.isFolia()) { - OrebfuscatorCompatibility.runAsyncNow(this.asyncListenerHandler.getListenerLoop()); - } else { - this.asyncListenerHandler.start(); - } - - var statistics = orebfuscator.getStatistics(); - statistics.setOriginalChunkSize(() -> (long) originalSize.average()); - statistics.setObfuscatedChunkSize(() -> (long) obfuscatedSize.average()); - } - - public void unregister() { - this.asynchronousManager.unregisterAsyncHandler(this.asyncListenerHandler); - } - - @Override - public void onPacketReceiving(PacketEvent event) { - event.getPacket().getFloat().write(0, 10f); - } - - @Override - public void onPacketSending(PacketEvent event) { - if (event.getPacket().getType() != PacketType.Play.Server.MAP_CHUNK) { - return; - } - - Player player = event.getPlayer(); - BukkitWorldAccessor worldAccessor = BukkitWorldAccessor.get(player.getWorld()); - if (this.shouldNotObfuscate(player, worldAccessor)) { - return; - } - - var packet = new BukkitChunkPacketAccessor(event.getPacket(), worldAccessor); - if (packet.isEmpty()) { - return; - } - - // delay packet - event.getAsyncMarker().incrementProcessingDelay(); - - CompletableFuture future = this.obfuscationSystem.obfuscate(packet); - - AdvancedConfig advancedConfig = this.config.advanced(); - if (advancedConfig.hasObfuscationTimeout()) { - future = future.orTimeout(advancedConfig.obfuscationTimeout(), TimeUnit.MILLISECONDS); - } - - future.whenComplete((chunk, throwable) -> { - if (throwable != null) { - this.completeExceptionally(event, packet, throwable); - } else if (chunk != null) { - this.complete(event, packet, chunk); - } else { - OfcLogger.warn(String.format("skipping chunk[world=%s, x=%d, z=%d] because obfuscation result is missing", - packet.worldAccessor.getName(), packet.chunkX(), packet.chunkZ())); - this.asynchronousManager.signalPacketTransmission(event); - } - }); - } - - private boolean shouldNotObfuscate(Player player, BukkitWorldAccessor worldAccessor) { - return PermissionUtil.canBypassObfuscate(player) || !config.world(worldAccessor).needsObfuscation(); - } - - private void completeExceptionally(PacketEvent event, BukkitChunkPacketAccessor packet, Throwable throwable) { - if (throwable instanceof TimeoutException) { - OfcLogger.warn(String.format("Obfuscation for chunk[world=%s, x=%d, z=%d] timed out", - packet.worldAccessor.getName(), packet.chunkX(), packet.chunkZ())); - } else { - OfcLogger.error(String.format("An error occurred while obfuscating chunk[world=%s, x=%d, z=%d]", - packet.worldAccessor.getName(), packet.chunkX(), packet.chunkZ()), throwable); - } - - this.asynchronousManager.signalPacketTransmission(event); - } - - private void complete(PacketEvent event, BukkitChunkPacketAccessor packet, ObfuscationResult chunk) { - originalSize.add(packet.data().length); - obfuscatedSize.add(chunk.getData().length); - - packet.setData(chunk.getData()); - - Set blockEntities = chunk.getBlockEntities(); - if (!blockEntities.isEmpty()) { - packet.filterBlockEntities(blockEntities::contains); - } - - final OrebfuscatorPlayer player = this.playerMap.get(event.getPlayer()); - if (player != null) { - player.addChunk(packet.chunkX(), packet.chunkZ(), chunk.getProximityBlocks()); - } - - this.asynchronousManager.signalPacketTransmission(event); - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java deleted file mode 100644 index 5e38d2ed..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationProcessor.java +++ /dev/null @@ -1,161 +0,0 @@ -package net.imprex.orebfuscator.obfuscation; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import dev.imprex.orebfuscator.chunk.Chunk; -import dev.imprex.orebfuscator.chunk.ChunkFactory; -import dev.imprex.orebfuscator.chunk.ChunkSection; -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; -import dev.imprex.orebfuscator.config.ProximityHeightCondition; -import dev.imprex.orebfuscator.config.api.BlockFlags; -import dev.imprex.orebfuscator.config.api.ObfuscationConfig; -import dev.imprex.orebfuscator.config.api.ProximityConfig; -import dev.imprex.orebfuscator.config.api.WorldConfigBundle; -import dev.imprex.orebfuscator.interop.WorldAccessor; -import dev.imprex.orebfuscator.util.BlockPos; -import net.imprex.orebfuscator.Orebfuscator; -import net.imprex.orebfuscator.OrebfuscatorNms; -import net.imprex.orebfuscator.iterop.BukkitChunkPacketAccessor; - -public class ObfuscationProcessor { - - private final OrebfuscatorConfig config; - private final ChunkFactory chunkFactory; - - public ObfuscationProcessor(Orebfuscator orebfuscator) { - this.config = orebfuscator.getOrebfuscatorConfig(); - this.chunkFactory = orebfuscator.getChunkFactory(); - } - - public void process(ObfuscationTask task) { - BukkitChunkPacketAccessor packet = task.getPacket(); - - WorldAccessor worldAccessor = packet.worldAccessor; - - WorldConfigBundle bundle = this.config.world(worldAccessor); - BlockFlags blockFlags = bundle.blockFlags(); - ObfuscationConfig obfuscationConfig = bundle.obfuscation(); - ProximityConfig proximityConfig = bundle.proximity(); - - Set blockEntities = new HashSet<>(); - List proximityBlocks = new ArrayList<>(); - - int baseX = packet.chunkX() << 4; - int baseZ = packet.chunkZ() << 4; - - int layerY = Integer.MIN_VALUE; - int layerYBlockState = -1; - - try (Chunk chunk = this.chunkFactory.fromPacket(packet)) { - for (int sectionIndex = Math.max(0, bundle.minSectionIndex()); sectionIndex <= Math - .min(chunk.getSectionCount() - 1, bundle.maxSectionIndex()); sectionIndex++) { - ChunkSection chunkSection = chunk.getSection(sectionIndex); - if (chunkSection == null || chunkSection.isEmpty()) { - continue; - } - - final int baseY = worldAccessor.getMinBuildHeight() + (sectionIndex << 4); - for (int index = 0; index < 4096; index++) { - int y = baseY + (index >> 8 & 15); - if (!bundle.shouldObfuscate(y)) { - continue; - } - - int blockState = chunkSection.getBlockState(index); - - int obfuscateBits = blockFlags.flags(blockState, y); - if (BlockFlags.isEmpty(obfuscateBits)) { - continue; - } - - int x = baseX + (index & 15); - int z = baseZ + (index >> 4 & 15); - - boolean isObfuscateBitSet = BlockFlags.isObfuscateBitSet(obfuscateBits); - boolean obfuscated = false; - - // should current block be obfuscated - if (isObfuscateBitSet && obfuscationConfig.shouldObfuscate(y) && shouldObfuscate(task, chunk, x, y, z)) { - if (obfuscationConfig.layerObfuscation()) { - if (layerY != y) { - layerY = y; - layerYBlockState = bundle.nextRandomObfuscationBlock(y); - } - blockState = layerYBlockState; - } else { - blockState = bundle.nextRandomObfuscationBlock(y); - } - obfuscated = true; - } - - // should current block be proximity hidden - if (!obfuscated && BlockFlags.isProximityBitSet(obfuscateBits) && proximityConfig.shouldObfuscate(y)) { - proximityBlocks.add(new BlockPos(x, y, z)); - if (BlockFlags.isUseBlockBelowBitSet(obfuscateBits)) { - boolean allowNonOcclude = !isObfuscateBitSet || !ProximityHeightCondition.isPresent(obfuscateBits); - blockState = getBlockStateBelow(bundle, chunk, x, y, z, allowNonOcclude); - } else { - blockState = bundle.nextRandomProximityBlock(y); - } - obfuscated = true; - } - - // update block state if needed - if (obfuscated) { - chunkSection.setBlockState(index, blockState); - if (BlockFlags.isBlockEntityBitSet(obfuscateBits)) { - blockEntities.add(new BlockPos(x, y, z)); - } - } - } - } - - task.complete(chunk.finalizeOutput(), blockEntities, proximityBlocks); - } catch (Exception e) { - task.completeExceptionally(e); - } - } - - // returns first block below given position that wouldn't be obfuscated in any - // way at given position - private int getBlockStateBelow(WorldConfigBundle bundle, Chunk chunk, int x, int y, int z, boolean allowNonOcclude) { - BlockFlags blockFlags = bundle.blockFlags(); - - for (int targetY = y - 1; targetY > chunk.world().getMinBuildHeight(); targetY--) { - int blockData = chunk.getBlockState(x, targetY, z); - if (blockData != -1 && (allowNonOcclude || OrebfuscatorNms.isOccluding(blockData))) { - int mask = blockFlags.flags(blockData, y); - if (BlockFlags.isEmpty(mask) || BlockFlags.isAllowForUseBlockBelowBitSet(mask)) { - return blockData; - } - } - } - - return bundle.nextRandomProximityBlock(y); - } - - private boolean shouldObfuscate(ObfuscationTask task, Chunk chunk, int x, int y, int z) { - return isAdjacentBlockOccluding(task, chunk, x, y + 1, z) - && isAdjacentBlockOccluding(task, chunk, x, y - 1, z) - && isAdjacentBlockOccluding(task, chunk, x + 1, y, z) - && isAdjacentBlockOccluding(task, chunk, x - 1, y, z) - && isAdjacentBlockOccluding(task, chunk, x, y, z + 1) - && isAdjacentBlockOccluding(task, chunk, x, y, z - 1); - } - - private boolean isAdjacentBlockOccluding(ObfuscationTask task, Chunk chunk, int x, int y, int z) { - if (y >= chunk.world().getMaxBuildHeight() || y < chunk.world().getMinBuildHeight()) { - return false; - } - - int blockId = chunk.getBlockState(x, y, z); - if (blockId == -1) { - blockId = task.getBlockState(x, y, z); - } - - return blockId >= 0 && OrebfuscatorNms.isOccluding(blockId); - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationRequest.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationRequest.java deleted file mode 100644 index 644127f7..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationRequest.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.imprex.orebfuscator.obfuscation; - -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; - -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; -import dev.imprex.orebfuscator.util.BlockPos; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import net.imprex.orebfuscator.iterop.BukkitChunkPacketAccessor; - -public class ObfuscationRequest { - - private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128(); - private static final byte[] EMPTY_HASH = new byte[0]; - - public static final int HASH_LENGTH = HASH_FUNCTION.bits() / Byte.SIZE; - - private static final byte[] hash(byte[] systemHash, byte[] data) { - return HASH_FUNCTION.newHasher().putBytes(systemHash).putBytes(data).hash().asBytes(); - } - - public static ObfuscationRequest fromChunk(BukkitChunkPacketAccessor packet, OrebfuscatorConfig config, - ObfuscationTaskDispatcher dispatcher) { - byte[] hash = config.cache().enabled() ? hash(config.systemHash(), packet.data()) : EMPTY_HASH; - return new ObfuscationRequest(dispatcher, packet, hash); - } - - private final CompletableFuture future = new CompletableFuture<>(); - - private final ObfuscationTaskDispatcher dispatcher; - private final BukkitChunkPacketAccessor packet; - - private final ChunkCacheKey chunkCacheKey; - private final byte[] chunkHash; - - private ObfuscationRequest(ObfuscationTaskDispatcher dispatcher, BukkitChunkPacketAccessor packet, byte[] chunkHash) { - this.dispatcher = dispatcher; - this.packet = packet; - - this.chunkCacheKey = new ChunkCacheKey(packet.world().getName(), packet.chunkX(), packet.chunkZ()); - this.chunkHash = chunkHash; - } - - public CompletableFuture getFuture() { - return future; - } - - public ChunkCacheKey getCacheKey() { - return chunkCacheKey; - } - - public byte[] getChunkHash() { - return chunkHash; - } - - public BukkitChunkPacketAccessor getPacket() { - return packet; - } - - public CompletableFuture submitForObfuscation() { - this.dispatcher.submitRequest(this); - return this.future; - } - - public ObfuscationResult createResult(byte[] data, Set blockEntities, List proximityBlocks) { - return new ObfuscationResult(this.chunkCacheKey, this.chunkHash, data, blockEntities, proximityBlocks); - } - - public CompletableFuture complete(ObfuscationResult result) { - this.future.complete(result); - return this.future; - } - - public CompletableFuture completeExceptionally(Throwable throwable) { - this.future.completeExceptionally(throwable); - return this.future; - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationResult.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationResult.java deleted file mode 100644 index 587ec42b..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationResult.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.imprex.orebfuscator.obfuscation; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import dev.imprex.orebfuscator.util.BlockPos; -import dev.imprex.orebfuscator.util.ChunkCacheKey; - -public class ObfuscationResult { - - private final ChunkCacheKey key; - - private final byte[] hash; - private final byte[] data; - - private final Set blockEntities; - private final List proximityBlocks; - - public ObfuscationResult(ChunkCacheKey key, byte[] hash, byte[] data) { - this(key, hash, data, new HashSet<>(), new ArrayList<>()); - } - - public ObfuscationResult(ChunkCacheKey key, byte[] hash, byte[] data, - Set blockEntities, List proximityBlocks) { - this.key = key; - this.hash = hash; - this.data = data; - this.blockEntities = blockEntities; - this.proximityBlocks = proximityBlocks; - } - - public ChunkCacheKey getCacheKey() { - return key; - } - - public byte[] getHash() { - return hash; - } - - public byte[] getData() { - return data; - } - - public Set getBlockEntities() { - return blockEntities; - } - - public List getProximityBlocks() { - return proximityBlocks; - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSyncListener.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSyncListener.java new file mode 100644 index 00000000..53d200fd --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSyncListener.java @@ -0,0 +1,61 @@ +package net.imprex.orebfuscator.obfuscation; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketEvent; +import dev.imprex.orebfuscator.PermissionRequirements; +import dev.imprex.orebfuscator.obfuscation.ObfuscationPipeline; +import net.imprex.orebfuscator.Orebfuscator; +import net.imprex.orebfuscator.iterop.BukkitChunkPacketAccessor; +import net.imprex.orebfuscator.iterop.BukkitPlayerAccessor; +import net.imprex.orebfuscator.iterop.BukkitPlayerAccessorManager; +import net.imprex.orebfuscator.iterop.BukkitWorldAccessor; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class ObfuscationSyncListener extends PacketAdapter { + + private final ObfuscationPipeline pipeline; + private final BukkitPlayerAccessorManager playerManager; + + private final ProtocolManager protocolManager; + + public ObfuscationSyncListener(Orebfuscator orebfuscator) { + super(orebfuscator, PacketType.Play.Server.MAP_CHUNK); + + this.pipeline = orebfuscator.obfuscationPipeline(); + this.playerManager = orebfuscator.playerManager(); + + this.protocolManager = ProtocolLibrary.getProtocolManager(); + this.protocolManager.addPacketListener(this); + } + + public void unregister() { + this.protocolManager.removePacketListener(this); + } + + @Override + public void onPacketSending(PacketEvent event) { + BukkitPlayerAccessor player = this.playerManager.tryGet(event.getPlayer()); + if (player == null || !player.isAlive()) { + return; + } + + BukkitWorldAccessor world = player.world(); + if (player.hasPermission(PermissionRequirements.BYPASS) || !world.config().needsObfuscation()) { + return; + } + + var packet = new BukkitChunkPacketAccessor(event.getPacket(), world); + if (packet.isEmpty()) { + return; + } + + var neighboringChunks = world.getNeighboringChunksNow(packet.chunkX(), packet.chunkZ()); + var future = pipeline.request(world, player, packet, neighboringChunks).toCompletableFuture(); + + player.obfuscationFuture(event, future); + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSystem.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSystem.java index 66e2344d..8aae36c1 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSystem.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationSystem.java @@ -1,58 +1,62 @@ package net.imprex.orebfuscator.obfuscation; +import dev.imprex.orebfuscator.obfuscation.DeobfuscationWorker; +import dev.imprex.orebfuscator.util.BlockPos; import java.util.Collection; -import java.util.concurrent.CompletableFuture; - -import org.bukkit.block.Block; - -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; import net.imprex.orebfuscator.Orebfuscator; -import net.imprex.orebfuscator.cache.ObfuscationCache; -import net.imprex.orebfuscator.iterop.BukkitChunkPacketAccessor; +import net.imprex.orebfuscator.iterop.BukkitWorldAccessorManager; +import org.bukkit.block.Block; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +@NullMarked public class ObfuscationSystem { private final Orebfuscator orebfuscator; - private final OrebfuscatorConfig config; - private final ObfuscationCache cache; - - private final ObfuscationProcessor processor; - private final ObfuscationTaskDispatcher dispatcher; - private ObfuscationListener listener; - + private final BukkitWorldAccessorManager worldManager; private final DeobfuscationWorker deobfuscationWorker; + private @Nullable ObfuscationSyncListener syncListener; + private @Nullable ObfuscationAsyncListener asyncListener; + public ObfuscationSystem(Orebfuscator orebfuscator) { this.orebfuscator = orebfuscator; - this.config = orebfuscator.getOrebfuscatorConfig(); - this.cache = orebfuscator.getObfuscationCache(); - - this.processor = new ObfuscationProcessor(orebfuscator); - this.dispatcher = new ObfuscationTaskDispatcher(orebfuscator, this.processor); + this.worldManager = orebfuscator.worldManager(); this.deobfuscationWorker = new DeobfuscationWorker(orebfuscator); - DeobfuscationListener.createAndRegister(orebfuscator, this.deobfuscationWorker); + DeobfuscationListener.createAndRegister(orebfuscator, this); } public void registerChunkListener() { - this.listener = new ObfuscationListener(orebfuscator); + this.syncListener = new ObfuscationSyncListener(orebfuscator); + this.asyncListener = new ObfuscationAsyncListener(orebfuscator); } - public CompletableFuture obfuscate(BukkitChunkPacketAccessor packet) { - ObfuscationRequest request = ObfuscationRequest.fromChunk(packet, this.config, this.dispatcher); - if (this.config.cache().enabled()) { - return this.cache.get(request); - } else { - return request.submitForObfuscation(); - } + public void deobfuscate(Block block) { + var world = this.worldManager.get(block.getWorld()); + var blockPos = new BlockPos(block.getX(), block.getY(), block.getZ()); + this.deobfuscationWorker.deobfuscate(world, blockPos); } public void deobfuscate(Collection blocks) { - this.deobfuscationWorker.deobfuscate(blocks, false); + if (blocks.isEmpty()) { + return; + } + + var world = this.worldManager.get(blocks.stream().findFirst().get().getWorld()); + var blockPos = blocks.stream() + .map(block -> new BlockPos(block.getX(), block.getY(), block.getZ())) + .toList(); + + this.deobfuscationWorker.deobfuscate(world, blockPos); } public void shutdown() { - this.listener.unregister(); - this.dispatcher.shutdown(); + if (this.syncListener != null) { + this.syncListener.unregister(); + } + if (this.asyncListener != null) { + this.asyncListener.unregister(); + } } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTask.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTask.java deleted file mode 100644 index 9d6423d2..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTask.java +++ /dev/null @@ -1,54 +0,0 @@ -package net.imprex.orebfuscator.obfuscation; - -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - -import org.bukkit.World; - -import dev.imprex.orebfuscator.util.BlockPos; -import dev.imprex.orebfuscator.util.ChunkCacheKey; -import dev.imprex.orebfuscator.util.ChunkDirection; -import net.imprex.orebfuscator.OrebfuscatorCompatibility; -import net.imprex.orebfuscator.iterop.BukkitChunkPacketAccessor; -import net.imprex.orebfuscator.nms.ReadOnlyChunk; - -public class ObfuscationTask { - - public static CompletableFuture fromRequest(ObfuscationRequest request) { - World world = request.getPacket().worldAccessor.world; - ChunkCacheKey key = request.getCacheKey(); - - return OrebfuscatorCompatibility.getNeighboringChunks(world, key) - .thenApply(chunks -> new ObfuscationTask(request, chunks)); - } - - private final ObfuscationRequest request; - private final ReadOnlyChunk[] neighboringChunks; - - private ObfuscationTask(ObfuscationRequest request, ReadOnlyChunk[] neighboringChunks) { - if (neighboringChunks == null || neighboringChunks.length != 4) { - throw new IllegalArgumentException("neighboringChunks missing or invalid length"); - } - - this.request = request; - this.neighboringChunks = neighboringChunks; - } - - public BukkitChunkPacketAccessor getPacket() { - return this.request.getPacket(); - } - - public void complete(byte[] data, Set blockEntities, List proximityBlocks) { - this.request.complete(this.request.createResult(data, blockEntities, proximityBlocks)); - } - - public void completeExceptionally(Throwable throwable) { - this.request.completeExceptionally(throwable); - } - - public int getBlockState(int x, int y, int z) { - ChunkDirection direction = ChunkDirection.fromPosition(request.getCacheKey(), x, z); - return this.neighboringChunks[direction.ordinal()].getBlockState(x, y, z); - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskDispatcher.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskDispatcher.java deleted file mode 100644 index 9bb68bff..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskDispatcher.java +++ /dev/null @@ -1,78 +0,0 @@ -package net.imprex.orebfuscator.obfuscation; - -import java.util.Arrays; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -import dev.imprex.orebfuscator.config.api.AdvancedConfig; -import net.imprex.orebfuscator.Orebfuscator; - -class ObfuscationTaskDispatcher { - - private static final long DEFAULT_PARK_DURATION = TimeUnit.MILLISECONDS.toNanos(1); - private static final long MAX_PARK_DURATION = TimeUnit.MILLISECONDS.toNanos(16); - - private final Queue tasks = new ConcurrentLinkedQueue<>(); - - private final ObfuscationProcessor processor; - private final ObfuscationTaskWorker[] worker; - - public ObfuscationTaskDispatcher(Orebfuscator orebfuscator, ObfuscationProcessor processor) { - this.processor = processor; - - AdvancedConfig config = orebfuscator.getOrebfuscatorConfig().advanced(); - this.worker = new ObfuscationTaskWorker[config.obfuscationThreads()]; - for (int i = 0; i < this.worker.length; i++) { - this.worker[i] = new ObfuscationTaskWorker(this, this.processor); - } - - var statistics = orebfuscator.getStatistics(); - statistics.setObfuscationQueueLengthSupplier(() -> this.tasks.size()); - statistics.setObfuscationWaitTime(() -> (long) Arrays.stream(this.worker) - .mapToDouble(ObfuscationTaskWorker::waitTime).average().orElse(0d)); - statistics.setObfuscationProcessTime(() -> (long) Arrays.stream(this.worker) - .mapToDouble(ObfuscationTaskWorker::processTime).average().orElse(0d)); - } - - public void submitRequest(ObfuscationRequest request) { - ObfuscationTask.fromRequest(request).whenComplete((task, throwable) -> { - if (throwable != null) { - request.completeExceptionally(throwable); - } else { - this.tasks.offer(task); - } - }); - } - - public ObfuscationTask retrieveTask() throws InterruptedException { - ObfuscationTask task; - - long parkDuration = DEFAULT_PARK_DURATION; - for (int i = 0; (task = this.tasks.poll()) == null; i++) { - if (i < 8192) { - Thread.onSpinWait(); - } else { - LockSupport.parkNanos(this, parkDuration); - - // exponential backoff - if (parkDuration < MAX_PARK_DURATION) { - parkDuration *= 2; - } - } - - if (Thread.interrupted()) { - throw new InterruptedException(); - } - } - - return task; - } - - public void shutdown() { - for (ObfuscationTaskWorker worker : this.worker) { - worker.shutdown(); - } - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskWorker.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskWorker.java deleted file mode 100644 index 61fbcbf2..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/ObfuscationTaskWorker.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.imprex.orebfuscator.obfuscation; - -import java.util.concurrent.atomic.AtomicInteger; - -import net.imprex.orebfuscator.Orebfuscator; -import net.imprex.orebfuscator.util.RingTimer; - -class ObfuscationTaskWorker implements Runnable { - - private static final AtomicInteger WORKER_ID = new AtomicInteger(); - - private final ObfuscationTaskDispatcher dispatcher; - private final ObfuscationProcessor processor; - - private final Thread thread; - private volatile boolean running = true; - - private final RingTimer waitTimer = new RingTimer(100); - private final RingTimer processTimer = new RingTimer(100); - - public ObfuscationTaskWorker(ObfuscationTaskDispatcher dispatcher, ObfuscationProcessor processor) { - this.dispatcher = dispatcher; - this.processor = processor; - - this.thread = new Thread(Orebfuscator.THREAD_GROUP, this, "ofc-task-worker-" + WORKER_ID.getAndIncrement()); - this.thread.setDaemon(true); - this.thread.start(); - } - - public double waitTime() { - return waitTimer.average(); - } - - public double processTime() { - return processTimer.average(); - } - - @Override - public void run() { - while (this.running) { - try { - ObfuscationTask task; - - waitTimer.start(); - try { - task = dispatcher.retrieveTask(); - } finally { - waitTimer.stop(); - } - - processTimer.start(); - try { - processor.process(task); - } finally { - processTimer.stop(); - } - } catch (InterruptedException e) { - break; - } - } - } - - public void shutdown() { - this.running = false; - this.thread.interrupt(); - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/PendingChunkBatch.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/PendingChunkBatch.java new file mode 100644 index 00000000..24e0cbbc --- /dev/null +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/obfuscation/PendingChunkBatch.java @@ -0,0 +1,58 @@ +package net.imprex.orebfuscator.obfuscation; + +import com.comphenix.protocol.AsynchronousManager; +import com.comphenix.protocol.events.PacketEvent; +import dev.imprex.orebfuscator.logging.OfcLogger; +import dev.imprex.orebfuscator.statistics.InjectorStatistics; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class PendingChunkBatch { + + private final long enqueuedAt = System.nanoTime(); + + private final InjectorStatistics statistics; + private final AsynchronousManager asynchronousManager; + + private final AtomicBoolean finished = new AtomicBoolean(false); + private final PacketEvent startPacket; + + private final List> pendingFutures = new ArrayList<>(); + + public PendingChunkBatch(InjectorStatistics statistics, AsynchronousManager asynchronousManager, + PacketEvent startPacket) { + this.statistics = statistics; + this.asynchronousManager = asynchronousManager; + + this.startPacket = startPacket; + startPacket.getAsyncMarker().incrementProcessingDelay(); + } + + public void addChunk(CompletableFuture future) { + if (!this.finished.get()) { + this.pendingFutures.add(future); + } + } + + public void finish() { + if (this.finished.compareAndSet(false, true)) { + var futures = this.pendingFutures.toArray(CompletableFuture[]::new); + + CompletableFuture.allOf(futures).whenComplete((v, throwable) -> { + if (throwable != null) { + OfcLogger.error("An error occurred while processing a chunk batch", throwable); + } + + // only delay/signal start packet as any packet after has to wait anyways and that way we + // only take up a single processing slot in ProtocolLib's async filter manager per batch + this.asynchronousManager.signalPacketTransmission(this.startPacket); + + statistics.packetDelayChunk.add(System.nanoTime() - this.enqueuedAt); + }); + } + } +} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayer.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayer.java deleted file mode 100644 index 4d343f31..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayer.java +++ /dev/null @@ -1,126 +0,0 @@ -package net.imprex.orebfuscator.player; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Player; - -import com.google.common.base.Objects; - -import dev.imprex.orebfuscator.config.api.AdvancedConfig; -import dev.imprex.orebfuscator.util.BlockPos; -import net.imprex.orebfuscator.Orebfuscator; - -public class OrebfuscatorPlayer { - - private static long chunkCoordsToLong(int chunkX, int chunkZ) { - return (chunkZ & 0xffffffffL) << 32 | chunkX & 0xffffffffL; - } - - private final Player player; - private final AdvancedConfig config; - - private final AtomicReference world = new AtomicReference<>(); - private final Map chunks = new ConcurrentHashMap<>(); - - private volatile long latestUpdateTimestamp = System.currentTimeMillis(); - private volatile Location location = new Location(null, 0, 0, 0); - - public OrebfuscatorPlayer(Orebfuscator orebfuscator, Player player) { - this.player = player; - this.config = orebfuscator.getOrebfuscatorConfig().advanced(); - this.location = player.getLocation(); - } - - /** - * Returns true if the last proximity update is longer ago then the configured proximity player interval (default 5s) - * or if the players location since the last update change according to the given rotation boolean and the - * {@link OrebfuscatorPlayer#isLocationSimilar isLocationSimilar} method. - * - * @param rotation passed to the isLocationSimilar method - * @return true if a proximity update is needed - */ - public boolean needsProximityUpdate(boolean rotation) { - if (!this.player.isOnline()) { - return false; - } - - long timestamp = System.currentTimeMillis(); - if (this.config.hasProximityPlayerCheckInterval() && - timestamp - this.latestUpdateTimestamp > this.config.proximityPlayerCheckInterval()) { - - // always update location + latestUpdateTimestamp on update - this.location = location; - this.latestUpdateTimestamp = timestamp; - - return true; - } - - Location location = this.player.getLocation(); - if (isLocationSimilar(rotation, this.location, location)) { - return false; - } - - // always update location + latestUpdateTimestamp on update - this.location = location; - this.latestUpdateTimestamp = timestamp; - - return true; - } - - /** - * Returns true if the worlds are the same and the distance between the locations is less then 0.5. If the rotation - * boolean is set this method also check if the yaw changed less then 10deg and the pitch less then 5deg. - * - * @param rotation should rotation be checked - * @param a the first location - * @param b the second location - * @return if the locations are similar - */ - private static boolean isLocationSimilar(boolean rotation, Location a, Location b) { - // check if world changed - if (!Objects.equal(a.getWorld(), b.getWorld())) { - return false; - } - - // check if len(xyz) changed less then 0.5 blocks - if (a.distanceSquared(b) > 0.25) { - return false; - } - - // check if rotation changed less then 5deg yaw or 2.5deg pitch - if (rotation && (Math.abs(a.getYaw() - b.getYaw()) > 5 || Math.abs(a.getPitch() - b.getPitch()) > 2.5)) { - return false; - } - - return true; - } - - void updateWorld() { - if (!this.player.isOnline()) { - return; - } - - World world = this.player.getWorld(); - if (!Objects.equal(this.world.getAndSet(world), world)) { - this.chunks.clear(); - } - } - - public void addChunk(int chunkX, int chunkZ, List blocks) { - this.chunks.put(chunkCoordsToLong(chunkX, chunkZ), - new OrebfuscatorPlayerChunk(chunkX, chunkZ, blocks)); - } - - public OrebfuscatorPlayerChunk getChunk(int chunkX, int chunkZ) { - return this.chunks.get(chunkCoordsToLong(chunkX, chunkZ)); - } - - public void removeChunk(int chunkX, int chunkZ) { - this.chunks.remove(chunkCoordsToLong(chunkX, chunkZ)); - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayerChunk.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayerChunk.java deleted file mode 100644 index acff2eb1..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayerChunk.java +++ /dev/null @@ -1,81 +0,0 @@ -package net.imprex.orebfuscator.player; - -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -import dev.imprex.orebfuscator.util.BlockPos; - -public class OrebfuscatorPlayerChunk { - - private final int chunkX; - private final int chunkZ; - - private int proximitySize; - private final int[] proximityBlocks; - - public OrebfuscatorPlayerChunk(int chunkX, int chunkZ, List proximityBlocks) { - this.chunkX = chunkX; - this.chunkZ = chunkZ; - - this.proximitySize = proximityBlocks.size(); - this.proximityBlocks = new int[proximityBlocks.size()]; - - for (int i = 0; i < proximityBlocks.size(); i++) { - this.proximityBlocks[i] = proximityBlocks.get(i).toSectionPos(); - } - } - - public boolean isEmpty() { - return proximitySize <= 0; - } - - public Iterator proximityIterator() { - return new ProximityItr(); - } - - private class ProximityItr implements Iterator { - - private final int x = chunkX << 4; - private final int z = chunkZ << 4; - - private int cursor; - private int returnCursor = -1; - - @Override - public boolean hasNext() { - return cursor < proximitySize; - } - - @Override - public BlockPos next() { - if (cursor >= proximitySize) { - throw new NoSuchElementException(); - } - - int sectionPos = proximityBlocks[returnCursor = cursor]; - cursor++; - - return BlockPos.fromSectionPos(x, z, sectionPos); - } - - @Override - public void remove() { - if (returnCursor < 0) { - throw new IllegalStateException(); - } - - // remove entry - final int index = returnCursor; - final int newSize; - if ((newSize = proximitySize - 1) > index) { - System.arraycopy(proximityBlocks, index + 1, proximityBlocks, index, newSize - index); - } - proximityBlocks[proximitySize = newSize] = 0xFFFFFFFF; - - // update cursor positions - cursor = returnCursor; - returnCursor = -1; - } - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayerMap.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayerMap.java deleted file mode 100644 index 2025bcf0..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/player/OrebfuscatorPlayerMap.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.imprex.orebfuscator.player; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -import net.imprex.orebfuscator.Orebfuscator; - -public class OrebfuscatorPlayerMap implements Listener { - - private final Orebfuscator orebfuscator; - - private final ConcurrentMap internalMap = new ConcurrentHashMap<>(); - - public OrebfuscatorPlayerMap(Orebfuscator orebfuscator) { - this.orebfuscator = orebfuscator; - if (orebfuscator.getOrebfuscatorConfig().proximityEnabled()) { - Bukkit.getPluginManager().registerEvents(this, orebfuscator); - - for (Player player : Bukkit.getOnlinePlayers()) { - this.addPlayer(player); - } - } - } - - private void addPlayer(Player player) { - this.internalMap.put(player, new OrebfuscatorPlayer(orebfuscator, player)); - } - - @EventHandler - public void onJoin(PlayerJoinEvent event) { - this.addPlayer(event.getPlayer()); - } - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - this.internalMap.remove(event.getPlayer()); - } - - public OrebfuscatorPlayer get(Player player) { - OrebfuscatorPlayer orebfuscatorPlayer = this.internalMap.get(player); - if (orebfuscatorPlayer != null) { - orebfuscatorPlayer.updateWorld(); - } - return orebfuscatorPlayer; - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityDirectorThread.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityDirectorThread.java deleted file mode 100644 index 0261e720..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityDirectorThread.java +++ /dev/null @@ -1,187 +0,0 @@ -package net.imprex.orebfuscator.proximity; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.Phaser; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; - -import dev.imprex.orebfuscator.config.api.AdvancedConfig; -import net.imprex.orebfuscator.Orebfuscator; -import dev.imprex.orebfuscator.logging.OfcLogger; -import net.imprex.orebfuscator.util.RingTimer; - -public class ProximityDirectorThread extends Thread implements Listener { - - private final Orebfuscator orebfuscator; - private final int workerCount; - private final int defaultBucketSize; - private final long checkInterval; - - private final Phaser phaser = new Phaser(1); - private volatile boolean running = true; - - private final ProximityWorker worker; - private final ProximityWorkerThread[] workerThreads; - - private final BlockingQueue> bucketQueue = new LinkedBlockingQueue<>(); - - private final RingTimer waitTimer = new RingTimer(100); - private final RingTimer processTimer = new RingTimer(100); - - public ProximityDirectorThread(Orebfuscator orebfuscator) { - super(Orebfuscator.THREAD_GROUP, "ofc-proximity-director"); - this.setDaemon(true); - - this.orebfuscator = orebfuscator; - - AdvancedConfig advancedConfig = orebfuscator.getOrebfuscatorConfig().advanced(); - this.workerCount = advancedConfig.proximityThreads(); - this.defaultBucketSize = advancedConfig.proximityDefaultBucketSize(); - this.checkInterval = TimeUnit.MILLISECONDS.toNanos(advancedConfig.proximityThreadCheckInterval()); - - this.worker = new ProximityWorker(orebfuscator); - this.workerThreads = new ProximityWorkerThread[workerCount - 1]; - - var statistics = this.orebfuscator.getStatistics(); - statistics.setProximityWaitTime(() -> (long) waitTimer.average()); - statistics.setProximityProcessTime(() -> (long) processTimer.average()); - } - - @EventHandler - public void onJoin(PlayerJoinEvent event) { - if (LockSupport.getBlocker(this) == this) { - LockSupport.unpark(this); - } - } - - @Override - public void start() { - Bukkit.getPluginManager().registerEvents(this, this.orebfuscator); - - super.start(); - - for (int i = 0; i < workerCount - 1; i++) { - this.workerThreads[i] = new ProximityWorkerThread(this, this.worker); - this.workerThreads[i].start(); - } - } - - public void close() { - this.running = false; - - this.interrupt(); - - for (int i = 0; i < workerCount - 1; i++) { - this.workerThreads[i].interrupt(); - } - - // technically not need but better be safe - this.phaser.forceTermination(); - } - - boolean isRunning() { - return this.running && !this.phaser.isTerminated(); - } - - List nextBucket() throws InterruptedException { - return this.bucketQueue.take(); - } - - void finishBucketProcessing() { - this.phaser.arriveAndDeregister(); - } - - @Override - public void run() { - while (this.isRunning()) { - try { - long processStart = System.nanoTime(); - Collection players = Bukkit.getOnlinePlayers(); - - // park thread if no players are online - if (players.isEmpty()) { - // park for 1sec and retry - LockSupport.parkNanos(this, 1000000000L); - // reset interrupt flag - Thread.interrupted(); - continue; - } - - // get player count and derive max bucket size for each thread - int playerCount = players.size(); - int maxBucketSize = Math.max(this.defaultBucketSize, (int) Math.ceil((float) playerCount / this.workerCount)); - - // calculate bucket - int bucketCount = (int) Math.ceil((float) playerCount / maxBucketSize); - int bucketSize = (int) Math.ceil((float) playerCount / (float) bucketCount); - - // register extra worker threads in phaser - if (bucketCount > 1) { - this.phaser.bulkRegister(bucketCount - 1); - } - - // this threads local bucket - List localBucket = null; - - Iterator iterator = players.iterator(); - - // create buckets and fill queue - for (int index = 0; index < bucketCount; index++) { - List bucket = new ArrayList<>(); - - // fill bucket until bucket full or no players remain - for (int size = 0; size < bucketSize && iterator.hasNext(); size++) { - bucket.add(iterator.next()); - } - - // assign first bucket to current thread - if (index == 0) { - localBucket = bucket; - } else { - this.bucketQueue.offer(bucket); - } - } - - // process local bucket - this.worker.process(localBucket); - - // wait for all threads to finish and reset phaser - this.phaser.awaitAdvanceInterruptibly(this.phaser.arrive()); - - long processTime = System.nanoTime() - processStart; - processTimer.add(processTime); - - // check if we have enough time to sleep - long waitTime = Math.max(0, this.checkInterval - processTime); - long waitMillis = TimeUnit.NANOSECONDS.toMillis(waitTime); - - if (waitMillis > 0) { - // measure wait time - waitTimer.add(TimeUnit.MILLISECONDS.toNanos(waitMillis)); - - Thread.sleep(waitMillis); - } - } catch (InterruptedException e) { - continue; - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (this.phaser.isTerminated() && this.running) { - OfcLogger.error("Looks like we encountered an invalid state, please report this:", - new IllegalStateException("Proximity Phaser terminated!")); - } - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityPacketListener.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityPacketListener.java index 07378f47..c04f6c5c 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityPacketListener.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityPacketListener.java @@ -1,7 +1,5 @@ package net.imprex.orebfuscator.proximity; -import org.bukkit.entity.Player; - import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolManager; @@ -10,34 +8,31 @@ import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.wrappers.ChunkCoordIntPair; - -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; +import dev.imprex.orebfuscator.PermissionRequirements; import dev.imprex.orebfuscator.config.api.ProximityConfig; -import dev.imprex.orebfuscator.interop.WorldAccessor; +import dev.imprex.orebfuscator.player.OrebfuscatorPlayer; import net.imprex.orebfuscator.Orebfuscator; +import net.imprex.orebfuscator.iterop.BukkitPlayerAccessor; +import net.imprex.orebfuscator.iterop.BukkitPlayerAccessorManager; import net.imprex.orebfuscator.iterop.BukkitWorldAccessor; -import net.imprex.orebfuscator.player.OrebfuscatorPlayer; -import net.imprex.orebfuscator.player.OrebfuscatorPlayerMap; import net.imprex.orebfuscator.util.MinecraftVersion; -import net.imprex.orebfuscator.util.PermissionUtil; +import org.jspecify.annotations.NullMarked; +@NullMarked public class ProximityPacketListener extends PacketAdapter { private static final boolean HAS_CHUNK_POS_FIELD = MinecraftVersion.isAtOrAbove("1.20.2"); + private final BukkitPlayerAccessorManager playerManager; private final ProtocolManager protocolManager; - private final OrebfuscatorConfig config; - private final OrebfuscatorPlayerMap playerMap; - public ProximityPacketListener(Orebfuscator orebfuscator) { super(orebfuscator, PacketType.Play.Server.UNLOAD_CHUNK); + this.playerManager = orebfuscator.playerManager(); + this.protocolManager = ProtocolLibrary.getProtocolManager(); this.protocolManager.addPacketListener(this); - - this.config = orebfuscator.getOrebfuscatorConfig(); - this.playerMap = orebfuscator.getPlayerMap(); } public void unregister() { @@ -46,27 +41,26 @@ public void unregister() { @Override public void onPacketSending(PacketEvent event) { - Player player = event.getPlayer(); - if (PermissionUtil.canBypassObfuscate(player)) { + BukkitPlayerAccessor player = this.playerManager.tryGet(event.getPlayer()); + if (player == null || player.hasPermission(PermissionRequirements.BYPASS)) { return; } - WorldAccessor worldAccessor = BukkitWorldAccessor.get(player.getWorld()); - ProximityConfig proximityConfig = config.world(worldAccessor).proximity(); + BukkitWorldAccessor world = player.world(); + ProximityConfig proximityConfig = world.config().proximity(); if (proximityConfig == null || !proximityConfig.isEnabled()) { return; } - OrebfuscatorPlayer orebfuscatorPlayer = this.playerMap.get(player); - if (orebfuscatorPlayer != null) { - PacketContainer packet = event.getPacket(); - if (HAS_CHUNK_POS_FIELD) { - ChunkCoordIntPair chunkPos = packet.getChunkCoordIntPairs().read(0); - orebfuscatorPlayer.removeChunk(chunkPos.getChunkX(), chunkPos.getChunkZ()); - } else { - StructureModifier ints = packet.getIntegers(); - orebfuscatorPlayer.removeChunk(ints.read(0), ints.read(1)); - } + PacketContainer packet = event.getPacket(); + OrebfuscatorPlayer orebfuscatorPlayer = player.orebfuscatorPlayer(); + + if (HAS_CHUNK_POS_FIELD) { + ChunkCoordIntPair chunkPos = packet.getChunkCoordIntPairs().read(0); + orebfuscatorPlayer.removeChunk(world, chunkPos.getChunkX(), chunkPos.getChunkZ()); + } else { + StructureModifier ints = packet.getIntegers(); + orebfuscatorPlayer.removeChunk(world, ints.read(0), ints.read(1)); } } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityWorker.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityWorker.java deleted file mode 100644 index 2fd3db41..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityWorker.java +++ /dev/null @@ -1,161 +0,0 @@ -package net.imprex.orebfuscator.proximity; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.joml.FrustumIntersection; -import org.joml.Quaternionf; - -import dev.imprex.orebfuscator.config.OrebfuscatorConfig; -import dev.imprex.orebfuscator.config.api.ProximityConfig; -import dev.imprex.orebfuscator.interop.WorldAccessor; -import dev.imprex.orebfuscator.util.BlockPos; -import net.imprex.orebfuscator.Orebfuscator; -import net.imprex.orebfuscator.OrebfuscatorCompatibility; -import net.imprex.orebfuscator.OrebfuscatorNms; -import net.imprex.orebfuscator.iterop.BukkitWorldAccessor; -import net.imprex.orebfuscator.player.OrebfuscatorPlayer; -import net.imprex.orebfuscator.player.OrebfuscatorPlayerChunk; -import net.imprex.orebfuscator.player.OrebfuscatorPlayerMap; -import net.imprex.orebfuscator.util.FastGazeUtil; -import net.imprex.orebfuscator.util.PermissionUtil; - -public class ProximityWorker { - - private final OrebfuscatorConfig config; - private final OrebfuscatorPlayerMap playerMap; - - public ProximityWorker(Orebfuscator orebfuscator) { - this.config = orebfuscator.getOrebfuscatorConfig(); - this.playerMap = orebfuscator.getPlayerMap(); - } - - private boolean shouldIgnorePlayer(Player player) { - if (PermissionUtil.canBypassObfuscate(player)) { - return true; - } - - return player.getGameMode() == GameMode.SPECTATOR && this.config.general().ignoreSpectator(); - } - - protected void process(List players) { - for (Player player : players) { - try { - this.process(player); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void process(Player player) { - if (this.shouldIgnorePlayer(player)) { - return; - } - - World world = player.getWorld(); - WorldAccessor worldAccessor = BukkitWorldAccessor.get(world); - - // check if world has enabled proximity config - ProximityConfig proximityConfig = this.config.world(worldAccessor).proximity(); - if (proximityConfig == null || !proximityConfig.isEnabled()) { - return; - } - - // frustum culling and ray casting both need rotation changes - boolean needsRotation = proximityConfig.frustumCullingEnabled() || proximityConfig.rayCastCheckEnabled(); - - // check if player changed location since last time - OrebfuscatorPlayer orebfuscatorPlayer = this.playerMap.get(player); - if (orebfuscatorPlayer == null || !orebfuscatorPlayer.needsProximityUpdate(needsRotation)) { - return; - } - - int distance = proximityConfig.distance(); - int distanceSquared = distance * distance; - - List updateBlocks = new ArrayList<>(); - Location eyeLocation = needsRotation - ? player.getEyeLocation() - : null; - - // create frustum planes if culling is enabled - FrustumIntersection frustum = proximityConfig.frustumCullingEnabled() - ? new FrustumIntersection(proximityConfig.frustumCullingProjectionMatrix() - .rotate(new Quaternionf() - .rotateX((float) Math.toRadians(eyeLocation.getPitch())) - .rotateY((float) Math.toRadians(eyeLocation.getYaw() + 180))) - .translate( - (float) -eyeLocation.getX(), - (float) -eyeLocation.getY(), - (float) -eyeLocation.getZ() - ), false) - : null; - - Location location = player.getLocation(); - int minChunkX = (location.getBlockX() - distance) >> 4; - int maxChunkX = (location.getBlockX() + distance) >> 4; - int minChunkZ = (location.getBlockZ() - distance) >> 4; - int maxChunkZ = (location.getBlockZ() + distance) >> 4; - - for (int chunkZ = minChunkZ; chunkZ <= maxChunkZ; chunkZ++) { - for (int chunkX = minChunkX; chunkX <= maxChunkX; chunkX++) { - - OrebfuscatorPlayerChunk chunk = orebfuscatorPlayer.getChunk(chunkX, chunkZ); - if (chunk == null) { - continue; - } - - for (Iterator iterator = chunk.proximityIterator(); iterator.hasNext(); ) { - BlockPos blockPos = iterator.next(); - - // check if block is in range - double blockDistanceSquared = blockPos.distanceSquared(location.getX(), location.getY(), location.getZ()); - if (blockDistanceSquared > distanceSquared) { - continue; - } - - // do frustum culling check - if (proximityConfig.frustumCullingEnabled() - && blockDistanceSquared > proximityConfig.frustumCullingMinDistanceSquared()) { - - // check if block AABB is inside frustum - int result = frustum.intersectAab( - blockPos.x(), blockPos.y(), blockPos.z(), - blockPos.x() + 1, blockPos.y() + 1, blockPos.z() + 1); - - // block is outside - if (result != FrustumIntersection.INSIDE && result != FrustumIntersection.INTERSECT) { - continue; - } - } - - // do ray cast check - if (proximityConfig.rayCastCheckEnabled() && !FastGazeUtil.doFastCheck(blockPos, eyeLocation, world, - proximityConfig.rayCastCheckOnlyCheckCenter())) { - continue; - } - - // block is visible and needs update - iterator.remove(); - updateBlocks.add(blockPos); - } - - if (chunk.isEmpty()) { - orebfuscatorPlayer.removeChunk(chunkX, chunkZ); - } - } - } - - OrebfuscatorCompatibility.runForPlayer(player, () -> { - if (player.isOnline() && player.getWorld().equals(world)) { - OrebfuscatorNms.sendBlockUpdates(player, updateBlocks); - } - }); - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityWorkerThread.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityWorkerThread.java deleted file mode 100644 index 2bb2949b..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/proximity/ProximityWorkerThread.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.imprex.orebfuscator.proximity; - -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import org.bukkit.entity.Player; - -import net.imprex.orebfuscator.Orebfuscator; - -public class ProximityWorkerThread extends Thread { - - private static final AtomicInteger NEXT_ID = new AtomicInteger(); - - private final ProximityDirectorThread directorThread; - private final ProximityWorker worker; - - public ProximityWorkerThread(ProximityDirectorThread directorThread, ProximityWorker worker) { - super(Orebfuscator.THREAD_GROUP, "ofc-proximity-worker-" + NEXT_ID.getAndIncrement()); - this.setDaemon(true); - - this.directorThread = directorThread; - this.worker = worker; - } - - @Override - public void run() { - while (this.directorThread.isRunning()) { - try { - List bucket = this.directorThread.nextBucket(); - - this.worker.process(bucket); - - this.directorThread.finishBucketProcessing(); - } catch (InterruptedException e) { - continue; - } catch (Exception e) { - e.printStackTrace(); - } - } - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/FastGazeUtil.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/FastGazeUtil.java deleted file mode 100644 index efdfca14..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/FastGazeUtil.java +++ /dev/null @@ -1,89 +0,0 @@ -package net.imprex.orebfuscator.util; - -import org.bukkit.Location; -import org.bukkit.World; - -import dev.imprex.orebfuscator.util.BlockPos; -import net.imprex.orebfuscator.OrebfuscatorNms; - -public class FastGazeUtil { - - /** - * Basic idea here is to take some rays from the considered block to the player's eyes, and decide if any of those - * rays can reach the eyes unimpeded. - * - * @param pos the starting block position - * @param eyes the destination eyes - * @param player the player world we are testing for - * @param onlyCheckCenter only check block center if true - * @return true if unimpeded path, false otherwise - */ - public static boolean doFastCheck(BlockPos pos, Location eyes, World player, boolean onlyCheckCenter) { - double ex = eyes.getX(); - double ey = eyes.getY(); - double ez = eyes.getZ(); - double x = pos.x(); - double y = pos.y(); - double z = pos.z(); - if (onlyCheckCenter) { - return // center - FastGazeUtil.fastAABBRayCheck(x, y, z, x + 0.5, y + 0.5, z + 0.5, ex, ey, ez, player); - } else { - return // midfaces - FastGazeUtil.fastAABBRayCheck(x, y, z, x, y + 0.5, z + 0.5, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 0.5, y, z + 0.5, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 0.5, y + 0.5, z, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 0.5, y + 1.0, z + 0.5, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 0.5, y + 0.5, z + 1.0, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 1.0, y + 0.5, z + 0.5, ex, ey, ez, player) || - // corners - FastGazeUtil.fastAABBRayCheck(x, y, z, x, y, z, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 1, y, z, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x, y + 1, z, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 1, y + 1, z, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x, y, z + 1, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 1, y, z + 1, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x, y + 1, z + 1, ex, ey, ez, player) - || FastGazeUtil.fastAABBRayCheck(x, y, z, x + 1, y + 1, z + 1, ex, ey, ez, player); - } - } - - public static boolean fastAABBRayCheck(double bx, double by, double bz, double x, double y, double z, double ex, - double ey, double ez, World world) { - double fx = ex - x; - double fy = ey - y; - double fz = ez - z; - double absFx = Math.abs(fx); - double absFy = Math.abs(fy); - double absFz = Math.abs(fz); - double s = Math.max(absFx, Math.max(absFy, absFz)); - - if (s < 1) { - return true; // on top / inside - } - - double lx, ly, lz; - - fx = fx / s; // units of change along vector - fy = fy / s; - fz = fz / s; - - while (s > 0) { - ex = ex - fx; // move along vector, we start _at_ the eye and move towards b - ey = ey - fy; - ez = ez - fz; - lx = Math.floor(ex); - ly = Math.floor(ey); - lz = Math.floor(ez); - if (lx == bx && ly == by && lz == bz) { - return true; // we've reached our starting block, don't test it. - } - int blockId = OrebfuscatorNms.getBlockState(world, (int) lx, (int) ly, (int) lz); - if (blockId != 0 && OrebfuscatorNms.isOccluding(blockId)) { - return false; // fail on first hit, this ray is "blocked" - } - s--; // we stop - } - return true; - } -} \ No newline at end of file diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/PermissionUtil.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/PermissionUtil.java index 9d7946d4..e4f67300 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/PermissionUtil.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/PermissionUtil.java @@ -1,19 +1,14 @@ package net.imprex.orebfuscator.util; +import dev.imprex.orebfuscator.PermissionRequirements; import org.bukkit.permissions.Permissible; +import org.jspecify.annotations.NullMarked; +@NullMarked public class PermissionUtil { - public static boolean canBypassObfuscate(Permissible permissible) { - try { - return permissible.hasPermission("orebfuscator.bypass"); - } catch (UnsupportedOperationException e) { - // fix #131: catch TemporaryPlayer not implementing hasPermission - return false; - } - } - - public static boolean canAccessAdminTools(Permissible permissible) { - return permissible.isOp() || permissible.hasPermission("orebfuscator.admin"); + public static boolean hasPermission(Permissible permissible, PermissionRequirements check) { + return (check.operatorLevel().isPresent() && permissible.isOp()) + || (check.permission().isPresent() && permissible.hasPermission(check.permission().get())); } } diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/RingTimer.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/RingTimer.java deleted file mode 100644 index c3b4e345..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/RingTimer.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.imprex.orebfuscator.util; - -public class RingTimer { - - private final long[] buffer; - - private int size; - private int head; - - private Long time; - - public RingTimer(int size) { - this.buffer = new long[size]; - } - - public double average() { - if (size == 0) { - return 0; - } - - double sum = 0; - for (int i = 0; i < size; i++) { - sum += buffer[i]; - } - - return sum / size; - } - - public void start() { - if (time == null) { - time = System.nanoTime(); - } - } - - public void stop() { - if (time != null) { - add(System.nanoTime() - time); - time = null; - } - } - - public void add(long time) { - buffer[head++] = time; - - if (head >= buffer.length) { - head = 0; - } - - if (size < buffer.length) { - size++; - } - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/RollingAverage.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/RollingAverage.java deleted file mode 100644 index 50afcfab..00000000 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/RollingAverage.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.imprex.orebfuscator.util; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.VarHandle; -import java.util.concurrent.atomic.AtomicInteger; - -public class RollingAverage { - - private static final VarHandle BUFFER_HANDLE = MethodHandles.arrayElementVarHandle(long[].class); - - private final long[] buffer; - - private final AtomicInteger head = new AtomicInteger(0); - private final AtomicInteger size = new AtomicInteger(0); - - public RollingAverage(int capacity) { - this.buffer = new long[capacity]; - } - - public void add(long value) { - int index = head.getAndUpdate(h -> (h + 1) % buffer.length); - BUFFER_HANDLE.setRelease(buffer, index, value); - - if (size.get() < buffer.length) { - size.updateAndGet(s -> s < buffer.length ? s + 1 : s); - } - } - - public double average() { - int size = this.size.get(); - if (size == 0) { - return 0; - } - - double sum = 0; - for (int i = 0; i < size; i++) { - sum += (long) BUFFER_HANDLE.getAcquire(buffer, i); - } - - return sum / size; - } -} diff --git a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/WrappedClientboundLevelChunkPacketData.java b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/WrappedClientboundLevelChunkPacketData.java index d6546dc3..4ff7b254 100644 --- a/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/WrappedClientboundLevelChunkPacketData.java +++ b/orebfuscator-plugin/src/main/java/net/imprex/orebfuscator/util/WrappedClientboundLevelChunkPacketData.java @@ -1,16 +1,17 @@ package net.imprex.orebfuscator.util; -import java.util.Iterator; -import java.util.List; -import java.util.function.Predicate; - import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.utility.MinecraftReflection; - import dev.imprex.orebfuscator.util.BlockPos; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; +import org.jspecify.annotations.NullMarked; +// TODO: replace all ProtocolLib reflections with own reflect lib +@NullMarked public class WrappedClientboundLevelChunkPacketData { private static final Class CLIENTBOUND_LEVEL_CHUNK_PACKET_DATA = MinecraftReflection.getMinecraftClass( diff --git a/pom.xml b/pom.xml index 4879ef82..19f34f1b 100644 --- a/pom.xml +++ b/pom.xml @@ -29,16 +29,17 @@ 4.1.90.Final - 33.4.8-jre - 2.13.1 - 2.4 + 33.5.0-jre + 2.13.2 + 2.5 1.10.8 - 1.8.0 + 1.10.2 5.13.4 5.19.0 26.0.2 + 1.0.0 3.14.0 3.6.0 @@ -221,5 +222,11 @@ ${dependency.jetannotations.version} provided + + org.jspecify + jspecify + ${dependency.jspecify.version} + compile +