0001-Add-custom-per-block-replacement.patch

0001-Add-custom-per-block-replacement.patch

unlisted ⁨1⁩ ⁨file⁩ 2023-02-02 14:57:44 UTC

0001-Add-custom-per-block-replacement.patch

Raw
From 9ef96155077f5830f6f2e3ea3605a6795438418f Mon Sep 17 00:00:00 2001
From: stonar96 <[email protected]>
Date: Thu, 2 Feb 2023 15:35:24 +0100
Subject: [PATCH] Add custom per block replacement


diff --git a/RayTraceAntiXray/src/main/java/com/vanillage/raytraceantixray/antixray/ChunkPacketBlockControllerAntiXray.java b/RayTraceAntiXray/src/main/java/com/vanillage/raytraceantixray/antixray/ChunkPacketBlockControllerAntiXray.java
index b3cd0ff..bc7d9f1 100644
--- a/RayTraceAntiXray/src/main/java/com/vanillage/raytraceantixray/antixray/ChunkPacketBlockControllerAntiXray.java
+++ b/RayTraceAntiXray/src/main/java/com/vanillage/raytraceantixray/antixray/ChunkPacketBlockControllerAntiXray.java
@@ -58,8 +58,8 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
     private final int[] presetBlockStateBitsNetherrackGlobal;
     private final int[] presetBlockStateBitsEndStoneGlobal;
     public final boolean[] solidGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
-    private final boolean[] obfuscateGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
-    private final boolean[] traceGlobal;
+    private final int[] obfuscateGlobal = new int[Block.BLOCK_STATE_REGISTRY.size()];
+    private final int[] traceGlobal;
     private final LevelChunkSection[] emptyNearbyChunkSections = {EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION, EMPTY_SECTION};
     public final boolean rayTraceThirdPerson;
     public final double rayTraceDistance;
@@ -76,9 +76,9 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         this.rayTraceThirdPerson = rayTraceThirdPerson;
         this.rayTraceDistance = rayTraceDistance;
         this.maxRayTraceBlockCountPerChunk = maxRayTraceBlockCountPerChunk;
-        List<String> toObfuscate;
+        List<String> toObfuscate = paperWorldConfig.hiddenBlocks;
 
-        if (engineMode == EngineMode.HIDE) {
+        /*if (engineMode == EngineMode.HIDE) {
             toObfuscate = paperWorldConfig.hiddenBlocks;
             presetBlockStates = null;
             presetBlockStatesFull = null;
@@ -124,16 +124,57 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
             presetBlockStateBitsDeepslateGlobal = null;
             presetBlockStateBitsNetherrackGlobal = null;
             presetBlockStateBitsEndStoneGlobal = null;
+        }*/
+
+        List<BlockState> presetBlockStateList = new LinkedList<>();
+        BlockState defaultBlockState;
+
+        switch (level.getWorld().getEnvironment()) {
+            case NETHER:
+                defaultBlockState = Blocks.NETHERRACK.defaultBlockState();
+                break;
+            case THE_END:
+                defaultBlockState = Blocks.END_STONE.defaultBlockState();
+                break;
+            default:
+                defaultBlockState = Blocks.STONE.defaultBlockState();
         }
 
+        // -1 means false (don't obfuscate).
+        Arrays.fill(obfuscateGlobal, -1);
+
         for (String id : toObfuscate) {
-            Block block = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(id)).orElse(null);
+            String fromToIdSeparator = "->";
+            int fromToIdSeparatorIndex = id.lastIndexOf(fromToIdSeparator);
+            String fromId = fromToIdSeparatorIndex >= 0 ? id.substring(0, fromToIdSeparatorIndex) : id;
+            Block block = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(fromId)).orElse(null);
 
             // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void
             if (block != null && !block.defaultBlockState().isAir()) {
+                BlockState toBlockState = null;
+
+                if (fromToIdSeparatorIndex >= 0) {
+                    Block toBlock = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(id.substring(fromToIdSeparatorIndex + fromToIdSeparator.length()))).orElse(null);
+
+                    if (toBlock != null && !(toBlock instanceof EntityBlock)) {
+                        toBlockState = toBlock.defaultBlockState();
+                    }
+                }
+
+                if (toBlockState == null) {
+                    toBlockState = defaultBlockState;
+                }
+
+                int presetBlockStateListIndex = presetBlockStateList.indexOf(toBlockState);
+
+                if (presetBlockStateListIndex < 0) {
+                    presetBlockStateListIndex = presetBlockStateList.size();
+                    presetBlockStateList.add(toBlockState);
+                }
+
                 // Replace all block states of a specified block
                 for (BlockState blockState : block.getStateDefinition().getPossibleStates()) {
-                    obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true;
+                    obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = presetBlockStateListIndex;
                 }
             }
         }
@@ -141,22 +182,69 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         if (toTrace == null) {
             traceGlobal = obfuscateGlobal;
         } else {
-            traceGlobal = new boolean[Block.BLOCK_STATE_REGISTRY.size()];
+            traceGlobal = new int[Block.BLOCK_STATE_REGISTRY.size()];
+            // -1 means false (don't obfuscate).
+            Arrays.fill(traceGlobal, -1);
 
             for (String id : toTrace) {
-                Block block = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(id)).orElse(null);
+                String fromToIdSeparator = "->";
+                int fromToIdSeparatorIndex = id.lastIndexOf(fromToIdSeparator);
+                String fromId = fromToIdSeparatorIndex >= 0 ? id.substring(0, fromToIdSeparatorIndex) : id;
+                Block block = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(fromId)).orElse(null);
 
                 // Don't obfuscate air because air causes unnecessary block updates and causes block updates to fail in the void
                 if (block != null && !block.defaultBlockState().isAir()) {
+                    BlockState toBlockState = null;
+
+                    if (fromToIdSeparatorIndex >= 0) {
+                        Block toBlock = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(id.substring(fromToIdSeparatorIndex + fromToIdSeparator.length()))).orElse(null);
+
+                        if (toBlock != null && !(toBlock instanceof EntityBlock)) {
+                            toBlockState = toBlock.defaultBlockState();
+                        }
+                    }
+
+                    if (toBlockState == null) {
+                        toBlockState = defaultBlockState;
+                    }
+
+                    int presetBlockStateListIndex = presetBlockStateList.indexOf(toBlockState);
+
+                    if (presetBlockStateListIndex < 0) {
+                        presetBlockStateListIndex = presetBlockStateList.size();
+                        presetBlockStateList.add(toBlockState);
+                    }
+
                     // Replace all block states of a specified block
                     for (BlockState blockState : block.getStateDefinition().getPossibleStates()) {
-                        traceGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true;
-                        obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = true;
+                        traceGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = presetBlockStateListIndex;
+                        obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] = presetBlockStateListIndex;
                     }
                 }
             }
         }
 
+        if (presetBlockStateList.isEmpty()) {
+            presetBlockStateList.add(defaultBlockState);
+        }
+
+        presetBlockStates = presetBlockStateList.toArray(new BlockState[0]);
+        presetBlockStatesFull = presetBlockStates;
+        presetBlockStatesStone = null;
+        presetBlockStatesDeepslate = null;
+        presetBlockStatesNetherrack = null;
+        presetBlockStatesEndStone = null;
+        presetBlockStateBitsGlobal = new int[presetBlockStatesFull.length];
+
+        for (int i = 0; i < presetBlockStatesFull.length; i++) {
+            presetBlockStateBitsGlobal[i] = GLOBAL_BLOCKSTATE_PALETTE.idFor(presetBlockStatesFull[i]);
+        }
+
+        presetBlockStateBitsStoneGlobal = null;
+        presetBlockStateBitsDeepslateGlobal = null;
+        presetBlockStateBitsNetherrackGlobal = null;
+        presetBlockStateBitsEndStoneGlobal = null;
+
         EmptyLevelChunk emptyChunk = new EmptyLevelChunk(level, new ChunkPos(0, 0), MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.PLAINS));
         BlockPos zeroPos = new BlockPos(0, 0, 0);
 
@@ -175,14 +263,14 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
     }
 
     private int getPresetBlockStatesFullLength() {
-        return engineMode == EngineMode.HIDE ? 1 : presetBlockStatesFull.length;
+        return presetBlockStatesFull.length;
     }
 
     @Override
     public BlockState[] getPresetBlockStates(Level level, ChunkPos chunkPos, int bottomBlockY) {
         // Return the block states to be added to the paletted containers so that they can be used for obfuscation
         if (bottomBlockY < maxBlockHeight) {
-            if (engineMode == EngineMode.HIDE) {
+            /*if (engineMode == EngineMode.HIDE) {
                 switch (level.getWorld().getEnvironment()) {
                     case NETHER:
                         return presetBlockStatesNetherrack;
@@ -191,7 +279,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
                     default:
                         return bottomBlockY < 0 ? presetBlockStatesDeepslate : presetBlockStatesStone;
                 }
-            }
+            }*/
 
             return presetBlockStates;
         }
@@ -235,22 +323,24 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
     // If an ExecutorService with multiple threads is used, ThreadLocal must be used here
     private final ThreadLocal<int[]> presetBlockStateBits = ThreadLocal.withInitial(() -> new int[getPresetBlockStatesFullLength()]);
     private static final ThreadLocal<boolean[]> SOLID = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
-    private static final ThreadLocal<boolean[]> OBFUSCATE = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
-    private static final ThreadLocal<boolean[]> TRACE = ThreadLocal.withInitial(() -> new boolean[Block.BLOCK_STATE_REGISTRY.size()]);
+    private static final ThreadLocal<int[]> OBFUSCATE = ThreadLocal.withInitial(() -> new int[Block.BLOCK_STATE_REGISTRY.size()]);
+    private static final ThreadLocal<int[]> TRACE = ThreadLocal.withInitial(() -> new int[Block.BLOCK_STATE_REGISTRY.size()]);
     // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate
     private static final ThreadLocal<boolean[][]> CURRENT = ThreadLocal.withInitial(() -> new boolean[16][16]);
     private static final ThreadLocal<boolean[][]> NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]);
     private static final ThreadLocal<boolean[][]> NEXT_NEXT = ThreadLocal.withInitial(() -> new boolean[16][16]);
+    private static final ThreadLocal<int[][]> OBFUSCATE_CACHE = ThreadLocal.withInitial(() -> new int[16][16]);
     private static final ThreadLocal<boolean[][]> TRACE_CACHE = ThreadLocal.withInitial(() -> new boolean[16][16]);
 
     public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) {
         int[] presetBlockStateBits = this.presetBlockStateBits.get();
         boolean[] solid = SOLID.get();
-        boolean[] obfuscate = OBFUSCATE.get();
-        boolean[] trace = traceGlobal == obfuscateGlobal ? obfuscate : TRACE.get();
+        int[] obfuscate = OBFUSCATE.get();
+        int[] trace = traceGlobal == obfuscateGlobal ? obfuscate : TRACE.get();
         boolean[][] current = CURRENT.get();
         boolean[][] next = NEXT.get();
         boolean[][] nextNext = NEXT_NEXT.get();
+        int[][] obfuscateCache = OBFUSCATE_CACHE.get();
         boolean[][] traceCache = TRACE_CACHE.get();
         // bitStorageReader, bitStorageWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it
         BitStorageReader bitStorageReader = new BitStorageReader();
@@ -260,8 +350,8 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         Level level = chunk.getLevel();
         int maxChunkSectionIndex = Math.min((maxBlockHeight >> 4) - chunk.getMinSection(), chunk.getSectionsCount()) - 1;
         boolean[] solidTemp = null;
-        boolean[] obfuscateTemp = null;
-        boolean[] traceTemp = null;
+        int[] obfuscateTemp = null;
+        int[] traceTemp = null;
         bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer());
         bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer());
         int numberOfBlocks = presetBlockStateBits.length;
@@ -290,7 +380,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
                 int[] presetBlockStateBitsTemp;
 
                 if (chunkPacketInfoAntiXray.getPalette(chunkSectionIndex) instanceof GlobalPalette) {
-                    if (engineMode == EngineMode.HIDE) {
+                    /*if (engineMode == EngineMode.HIDE) {
                         switch (level.getWorld().getEnvironment()) {
                             case NETHER:
                                 presetBlockStateBitsTemp = presetBlockStateBitsNetherrackGlobal;
@@ -301,9 +391,9 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
                             default:
                                 presetBlockStateBitsTemp = chunkSectionIndex + chunk.getMinSection() < 0 ? presetBlockStateBitsDeepslateGlobal : presetBlockStateBitsStoneGlobal;
                         }
-                    } else {
+                    } else {*/
                         presetBlockStateBitsTemp = presetBlockStateBitsGlobal;
-                    }
+                    //}
                 } else {
                     // If it's presetBlockStates, use this.presetBlockStatesFull instead
                     BlockState[] presetBlockStatesFull = chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex) == presetBlockStates ? this.presetBlockStatesFull : chunkPacketInfoAntiXray.getPresetValues(chunkSectionIndex);
@@ -340,7 +430,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
                     // Abuse the obfuscateLayer method to read the blocks of the first layer of the current chunk section
                     bitStorageWriter.setBits(0);
-                    obfuscateLayer(chunk.getPos(), chunk.getMinSection(), chunkSectionIndex, -1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, traceTemp, presetBlockStateBitsTemp, current, next, nextNext, traceCache, emptyNearbyChunkSections, random, blocks);
+                    obfuscateLayer(chunk.getPos(), chunk.getMinSection(), chunkSectionIndex, -1, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, traceTemp, presetBlockStateBitsTemp, current, next, nextNext, obfuscateCache, traceCache, emptyNearbyChunkSections, random, blocks);
                 }
 
                 bitStorageWriter.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex));
@@ -355,7 +445,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
                     current = next;
                     next = nextNext;
                     nextNext = temp;
-                    obfuscateLayer(chunk.getPos(), chunk.getMinSection(), chunkSectionIndex, y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, traceTemp, presetBlockStateBitsTemp, current, next, nextNext, traceCache, nearbyChunkSections, random, blocks);
+                    obfuscateLayer(chunk.getPos(), chunk.getMinSection(), chunkSectionIndex, y, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, traceTemp, presetBlockStateBitsTemp, current, next, nextNext, obfuscateCache, traceCache, nearbyChunkSections, random, blocks);
                 }
 
                 // Check if the chunk section above doesn't need obfuscation
@@ -378,7 +468,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
                     // There is nothing to read anymore
                     bitStorageReader.setBits(0);
                     solid[0] = true;
-                    obfuscateLayer(chunk.getPos(), chunk.getMinSection(), chunkSectionIndex, 15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, traceTemp, presetBlockStateBitsTemp, current, next, nextNext, traceCache, nearbyChunkSections, random, blocks);
+                    obfuscateLayer(chunk.getPos(), chunk.getMinSection(), chunkSectionIndex, 15, bitStorageReader, bitStorageWriter, solid, obfuscateTemp, traceTemp, presetBlockStateBitsTemp, current, next, nextNext, obfuscateCache, traceCache, nearbyChunkSections, random, blocks);
                 } else {
                     // If not, initialize the reader and other stuff for the chunk section above to obfuscate the upper layer of the current chunk section
                     bitStorageReader.setBits(chunkPacketInfoAntiXray.getBits(chunkSectionIndex + 1));
@@ -390,7 +480,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
                     current = next;
                     next = nextNext;
                     nextNext = temp;
-                    obfuscateLayer(chunk.getPos(), chunk.getMinSection(), chunkSectionIndex, 15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, traceTemp, presetBlockStateBitsTemp, current, next, nextNext, traceCache, nearbyChunkSections, random, blocks);
+                    obfuscateLayer(chunk.getPos(), chunk.getMinSection(), chunkSectionIndex, 15, bitStorageReader, bitStorageWriter, solidTemp, obfuscateTemp, traceTemp, presetBlockStateBitsTemp, current, next, nextNext, obfuscateCache, traceCache, nearbyChunkSections, random, blocks);
                 }
 
                 bitStorageWriter.flush();
@@ -404,7 +494,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         chunkPacketInfoAntiXray.getChunkPacket().setReady(true);
     }
 
-    private void obfuscateLayer(ChunkPos chunkPos, int minSection, int chunkSectionIndex, int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, boolean[] obfuscate, boolean[] trace, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, boolean[][] traceCache, LevelChunkSection[] nearbyChunkSections, IntSupplier random, Collection<? super BlockPos> blocks) {
+    private void obfuscateLayer(ChunkPos chunkPos, int minSection, int chunkSectionIndex, int y, BitStorageReader bitStorageReader, BitStorageWriter bitStorageWriter, boolean[] solid, int[] obfuscate, int[] trace, int[] presetBlockStateBits, boolean[][] current, boolean[][] next, boolean[][] nextNext, int[][] obfuscateCache, boolean[][] traceCache, LevelChunkSection[] nearbyChunkSections, IntSupplier random, Collection<? super BlockPos> blocks) {
         int minX = chunkPos.getMinBlockX();
         int minZ = chunkPos.getMinBlockZ();
         int realY = (chunkSectionIndex + minSection << 4) + y;
@@ -413,7 +503,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
         if (nextNext[0][0] = !solid[bits]) {
             if (traceCache[0][0] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][0]]); // Exposed to air
                 blocks.add(new BlockPos(minX + 0, realY, minZ + 0));
             } else {
                 bitStorageWriter.skip();
@@ -424,23 +514,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         } else {
             if (current[0][0] || isTransparent(nearbyChunkSections[2], 0, y, 15) || isTransparent(nearbyChunkSections[0], 15, y, 0)) {
                 if (traceCache[0][0] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][0]]); // Exposed to air
                     blocks.add(new BlockPos(minX + 0, realY, minZ + 0));
                 } else {
                     bitStorageWriter.skip();
                 }
             } else {
-                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][0]]); // Not exposed to air
             }
         }
 
-        if (trace[bits]) {
+        int obfuscateValue = trace[bits];
+
+        if (obfuscateValue != -1) {
+            obfuscateCache[0][0] = obfuscateValue;
             traceCache[0][0] = true;
         } else {
             traceCache[0][0] = false;
+            obfuscateValue = obfuscate[bits];
 
-            if (!obfuscate[bits]) {
+            if (obfuscateValue == -1) {
                 next[0][0] = true;
+            } else {
+                obfuscateCache[0][0] = obfuscateValue;
             }
         }
 
@@ -450,7 +546,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
             if (nextNext[0][x] = !solid[bits]) {
                 if (traceCache[0][x] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][x]]); // Exposed to air
                     blocks.add(new BlockPos(minX + x, realY, minZ + 0));
                 } else {
                     bitStorageWriter.skip();
@@ -462,23 +558,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
             } else {
                 if (current[0][x] || isTransparent(nearbyChunkSections[2], x, y, 15)) {
                     if (traceCache[0][x] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                        bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                        bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][x]]); // Exposed to air
                         blocks.add(new BlockPos(minX + x, realY, minZ + 0));
                     } else {
                         bitStorageWriter.skip();
                     }
                 } else {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][x]]); // Not exposed to air
                 }
             }
 
-            if (trace[bits]) {
+            obfuscateValue = trace[bits];
+
+            if (obfuscateValue != -1) {
+                obfuscateCache[0][x] = obfuscateValue;
                 traceCache[0][x] = true;
             } else {
                 traceCache[0][x] = false;
+                obfuscateValue = obfuscate[bits];
 
-                if (!obfuscate[bits]) {
+                if (obfuscateValue == -1) {
                     next[0][x] = true;
+                } else {
+                    obfuscateCache[0][x] = obfuscateValue;
                 }
             }
         }
@@ -488,7 +590,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
         if (nextNext[0][15] = !solid[bits]) {
             if (traceCache[0][15] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][15]]); // Exposed to air
                 blocks.add(new BlockPos(minX + 15, realY, minZ + 0));
             } else {
                 bitStorageWriter.skip();
@@ -499,23 +601,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         } else {
             if (current[0][15] || isTransparent(nearbyChunkSections[2], 15, y, 15) || isTransparent(nearbyChunkSections[1], 0, y, 0)) {
                 if (traceCache[0][15] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][15]]); // Exposed to air
                     blocks.add(new BlockPos(minX + 15, realY, minZ + 0));
                 } else {
                     bitStorageWriter.skip();
                 }
             } else {
-                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                bitStorageWriter.write(presetBlockStateBits[obfuscateCache[0][15]]); // Not exposed to air
             }
         }
 
-        if (trace[bits]) {
+        obfuscateValue = trace[bits];
+
+        if (obfuscateValue != -1) {
+            obfuscateCache[0][15] = obfuscateValue;
             traceCache[0][15] = true;
         } else {
             traceCache[0][15] = false;
+            obfuscateValue = obfuscate[bits];
 
-            if (!obfuscate[bits]) {
+            if (obfuscateValue == -1) {
                 next[0][15] = true;
+            } else {
+                obfuscateCache[0][15] = obfuscateValue;
             }
         }
 
@@ -526,7 +634,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
             if (nextNext[z][0] = !solid[bits]) {
                 if (traceCache[z][0] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][0]]); // Exposed to air
                     blocks.add(new BlockPos(minX + 0, realY, minZ + z));
                 } else {
                     bitStorageWriter.skip();
@@ -538,23 +646,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
             } else {
                 if (current[z][0] || isTransparent(nearbyChunkSections[0], 15, y, z)) {
                     if (traceCache[z][0] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                        bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                        bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][0]]); // Exposed to air
                         blocks.add(new BlockPos(minX + 0, realY, minZ + z));
                     } else {
                         bitStorageWriter.skip();
                     }
                 } else {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][0]]); // Not exposed to air
                 }
             }
 
-            if (trace[bits]) {
+            obfuscateValue = trace[bits];
+
+            if (obfuscateValue != -1) {
+                obfuscateCache[z][0] = obfuscateValue;
                 traceCache[z][0] = true;
             } else {
                 traceCache[z][0] = false;
+                obfuscateValue = obfuscate[bits];
 
-                if (!obfuscate[bits]) {
+                if (obfuscateValue == -1) {
                     next[z][0] = true;
+                } else {
+                    obfuscateCache[z][0] = obfuscateValue;
                 }
             }
 
@@ -564,7 +678,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
                 if (nextNext[z][x] = !solid[bits]) {
                     if (traceCache[z][x] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                        bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                        bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][x]]); // Exposed to air
                         blocks.add(new BlockPos(minX + x, realY, minZ + z));
                     } else {
                         bitStorageWriter.skip();
@@ -577,23 +691,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
                 } else {
                     if (current[z][x]) {
                         if (traceCache[z][x] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                            bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                            bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][x]]); // Exposed to air
                             blocks.add(new BlockPos(minX + x, realY, minZ + z));
                         } else {
                             bitStorageWriter.skip();
                         }
                     } else {
-                        bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                        bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][x]]); // Not exposed to air
                     }
                 }
 
-                if (trace[bits]) {
+                obfuscateValue = trace[bits];
+
+                if (obfuscateValue != -1) {
+                    obfuscateCache[z][x] = obfuscateValue;
                     traceCache[z][x] = true;
                 } else {
                     traceCache[z][x] = false;
+                    obfuscateValue = obfuscate[bits];
 
-                    if (!obfuscate[bits]) {
+                    if (obfuscateValue == -1) {
                         next[z][x] = true;
+                    } else {
+                        obfuscateCache[z][x] = obfuscateValue;
                     }
                 }
             }
@@ -603,7 +723,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
             if (nextNext[z][15] = !solid[bits]) {
                 if (traceCache[z][15] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][15]]); // Exposed to air
                     blocks.add(new BlockPos(minX + 15, realY, minZ + z));
                 } else {
                     bitStorageWriter.skip();
@@ -615,23 +735,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
             } else {
                 if (current[z][15] || isTransparent(nearbyChunkSections[1], 0, y, z)) {
                     if (traceCache[z][15] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                        bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                        bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][15]]); // Exposed to air
                         blocks.add(new BlockPos(minX + 15, realY, minZ + z));
                     } else {
                         bitStorageWriter.skip();
                     }
                 } else {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[z][15]]); // Not exposed to air
                 }
             }
 
-            if (trace[bits]) {
+            obfuscateValue = trace[bits];
+
+            if (obfuscateValue != -1) {
+                obfuscateCache[z][15] = obfuscateValue;
                 traceCache[z][15] = true;
             } else {
                 traceCache[z][15] = false;
+                obfuscateValue = obfuscate[bits];
 
-                if (!obfuscate[bits]) {
+                if (obfuscateValue == -1) {
                     next[z][15] = true;
+                } else {
+                    obfuscateCache[z][15] = obfuscateValue;
                 }
             }
         }
@@ -641,7 +767,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
         if (nextNext[15][0] = !solid[bits]) {
             if (traceCache[15][0] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][0]]); // Exposed to air
                 blocks.add(new BlockPos(minX + 0, realY, minZ + 15));
             } else {
                 bitStorageWriter.skip();
@@ -652,23 +778,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         } else {
             if (current[15][0] || isTransparent(nearbyChunkSections[3], 0, y, 0) || isTransparent(nearbyChunkSections[0], 15, y, 15)) {
                 if (traceCache[15][0] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][0]]); // Exposed to air
                     blocks.add(new BlockPos(minX + 0, realY, minZ + 15));
                 } else {
                     bitStorageWriter.skip();
                 }
             } else {
-                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][0]]); // Not exposed to air
             }
         }
 
-        if (trace[bits]) {
+        obfuscateValue = trace[bits];
+
+        if (obfuscateValue != -1) {
+            obfuscateCache[15][0] = obfuscateValue;
             traceCache[15][0] = true;
         } else {
             traceCache[15][0] = false;
+            obfuscateValue = obfuscate[bits];
 
-            if (!obfuscate[bits]) {
+            if (obfuscateValue == -1) {
                 next[15][0] = true;
+            } else {
+                obfuscateCache[15][0] = obfuscateValue;
             }
         }
 
@@ -678,7 +810,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
             if (nextNext[15][x] = !solid[bits]) {
                 if (traceCache[15][x] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][x]]); // Exposed to air
                     blocks.add(new BlockPos(minX + x, realY, minZ + 15));
                 } else {
                     bitStorageWriter.skip();
@@ -690,23 +822,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
             } else {
                 if (current[15][x] || isTransparent(nearbyChunkSections[3], x, y, 0)) {
                     if (traceCache[15][x] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                        bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                        bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][x]]); // Exposed to air
                         blocks.add(new BlockPos(minX + x, realY, minZ + 15));
                     } else {
                         bitStorageWriter.skip();
                     }
                 } else {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][x]]); // Not exposed to air
                 }
             }
 
-            if (trace[bits]) {
+            obfuscateValue = trace[bits];
+
+            if (obfuscateValue != -1) {
+                obfuscateCache[15][x] = obfuscateValue;
                 traceCache[15][x] = true;
             } else {
                 traceCache[15][x] = false;
+                obfuscateValue = obfuscate[bits];
 
-                if (!obfuscate[bits]) {
+                if (obfuscateValue == -1) {
                     next[15][x] = true;
+                } else {
+                    obfuscateCache[15][x] = obfuscateValue;
                 }
             }
         }
@@ -716,7 +854,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
 
         if (nextNext[15][15] = !solid[bits]) {
             if (traceCache[15][15] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][15]]); // Exposed to air
                 blocks.add(new BlockPos(minX + 15, realY, minZ + 15));
             } else {
                 bitStorageWriter.skip();
@@ -727,23 +865,29 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         } else {
             if (current[15][15] || isTransparent(nearbyChunkSections[3], 15, y, 0) || isTransparent(nearbyChunkSections[1], 0, y, 15)) {
                 if (traceCache[15][15] && blocks.size() < maxRayTraceBlockCountPerChunk) {
-                    bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Exposed to air
+                    bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][15]]); // Exposed to air
                     blocks.add(new BlockPos(minX + 15, realY, minZ + 15));
                 } else {
                     bitStorageWriter.skip();
                 }
             } else {
-                bitStorageWriter.write(presetBlockStateBits[random.getAsInt()]); // Not exposed to air
+                bitStorageWriter.write(presetBlockStateBits[obfuscateCache[15][15]]); // Not exposed to air
             }
         }
 
-        if (trace[bits]) {
+        obfuscateValue = trace[bits];
+
+        if (obfuscateValue != -1) {
+            obfuscateCache[15][15] = obfuscateValue;
             traceCache[15][15] = true;
         } else {
             traceCache[15][15] = false;
+            obfuscateValue = obfuscate[bits];
 
-            if (!obfuscate[bits]) {
+            if (obfuscateValue == -1) {
                 next[15][15] = true;
+            } else {
+                obfuscateCache[15][15] = obfuscateValue;
             }
         }
     }
@@ -782,6 +926,25 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
         return temp;
     }
 
+    private int[] readPalette(Palette<BlockState> palette, int[] temp, int[] global) {
+        if (palette instanceof GlobalPalette) {
+            return global;
+        }
+
+        try {
+            for (int i = 0; i < palette.getSize(); i++) {
+                temp[i] = global[GLOBAL_BLOCKSTATE_PALETTE.idFor(palette.valueFor(i))];
+            }
+        } catch (MissingPaletteEntryException e) {
+            // Race condition / visibility issue / no happens-before relationship
+            // We don't care because we at least see the state as it was when the chunk packet was created
+            // Internal implementation details of PalettedContainer, LinearPalette, HashMapPalette, CrudeIncrementalIntIdentityHashBiMap, ... guarantee us that no (other) exceptions will occur until we have all the data that we need here
+            // Since all palettes have a fixed initial maximum size and there is no internal restructuring and no values are removed from palettes, we are also guaranteed to see the data
+        }
+
+        return temp;
+    }
+
     @Override
     public void onBlockChange(Level level, BlockPos blockPos, BlockState newBlockState, BlockState oldBlockState, int flags, int maxUpdateDepth) {
         if (oldBlockState != null && solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(oldBlockState)] && !solidGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(newBlockState)] && blockPos.getY() <= maxBlockHeightUpdatePosition) {
@@ -838,7 +1001,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
     public void updateBlock(Level level, BlockPos blockPos) {
         BlockState blockState = level.getBlockStateIfLoaded(blockPos);
 
-        if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)]) {
+        if (blockState != null && obfuscateGlobal[GLOBAL_BLOCKSTATE_PALETTE.idFor(blockState)] != -1) {
             ((ServerLevel) level).getChunkSource().blockChanged(blockPos);
         }
     }