Revisions for ⁨0001-Add-custom-per-block-replacement.patch⁩

View the changes made to this paste.

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

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

@@ -0,0 +1,721 @@

+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);
+         }
+     }