From 95a0f31bec5c22a06e1c177886abad53a048bc7f Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:02:25 +0200 Subject: [PATCH 1/9] Use UpdateSubChunkBlocksPacket for ClientboundSectionBlocksUpdatePacket --- .../geyser/session/cache/ChunkCache.java | 56 ++++++++--------- .../JavaSectionBlocksUpdateTranslator.java | 61 ++++++++++++++++++- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java index 464b993836b..1badfe60344 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java @@ -27,8 +27,8 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import lombok.Getter; import lombok.Setter; -import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.chunk.GeyserChunk; import org.geysermc.geyser.registry.BlockRegistries; @@ -37,6 +37,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.chunk.DataPalette; public class ChunkCache { + @Getter private final boolean cache; private final Long2ObjectMap chunks; @@ -68,41 +69,40 @@ private GeyserChunk getChunk(int chunkX, int chunkZ) { return chunks.getOrDefault(chunkPosition, null); } - public void updateBlock(int x, int y, int z, int block) { - if (!cache) { - return; + /** + * Doesn't check for cache enabled, so don't use this without checking that first! + */ + @Deprecated + public DataPalette getChunkSection(int chunkX, int chunkY, int chunkZ) { + GeyserChunk chunk = this.getChunk(chunkX, chunkZ); + if (chunk == null) { + return null; } - GeyserChunk chunk = this.getChunk(x >> 4, z >> 4); - if (chunk == null) { - return; + if (chunkY < getChunkMinY() || chunkY - getChunkMinY() > chunk.sections().length - 1) { + return null; } - if (y < minY || ((y - minY) >> 4) > chunk.sections().length - 1) { - // Y likely goes above or below the height limit of this world + DataPalette palette = chunk.sections()[chunkY - getChunkMinY()]; + if (palette == null) { + palette = DataPalette.createForBlockState(Block.JAVA_AIR_ID, BlockRegistries.BLOCK_STATES.get().size()); + chunk.sections()[chunkY - getChunkMinY()] = palette; + } + + return palette; + } + + public void updateBlock(int x, int y, int z, int block) { + if (!cache) { return; } - boolean previouslyEmpty = false; - try { - DataPalette palette = chunk.sections()[(y - minY) >> 4]; - if (palette == null) { - previouslyEmpty = true; - if (block != Block.JAVA_AIR_ID) { - // A previously empty chunk, which is no longer empty as a block has been added to it - palette = DataPalette.createForBlockState(Block.JAVA_AIR_ID, BlockRegistries.BLOCK_STATES.get().size()); - chunk.sections()[(y - minY) >> 4] = palette; - } else { - // Nothing to update - return; - } - } - - palette.set(x & 0xF, y & 0xF, z & 0xF, block); - } catch (Throwable e) { - GeyserImpl.getInstance().getLogger().error("Failed to update block in chunk cache! ", e); - GeyserImpl.getInstance().getLogger().error("Info: newChunk=%s, block=%s, pos=%s,%s,%s".formatted(previouslyEmpty, block, x, y, z)); + DataPalette palette = this.getChunkSection(x >> 4, y >> 4, z >> 4); + if (palette == null) { + return; } + + palette.set(x & 0xF, y & 0xF, z & 0xF, block); } public int getBlockAt(int x, int y, int z) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java index a52bb33b0ed..9c2652e8603 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java @@ -25,6 +25,13 @@ package org.geysermc.geyser.translator.protocol.java.level; +import org.cloudburstmc.protocol.bedrock.packet.UpdateSubChunkBlocksPacket; +import org.geysermc.geyser.entity.type.ItemFrameEntity; +import org.geysermc.geyser.level.block.Blocks; +import org.geysermc.geyser.level.block.type.BlockState; +import org.geysermc.geyser.level.block.type.SkullBlock; +import org.geysermc.geyser.registry.BlockRegistries; +import org.geysermc.mcprotocollib.protocol.data.game.chunk.DataPalette; import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockChangeEntry; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundSectionBlocksUpdatePacket; import org.geysermc.geyser.session.GeyserSession; @@ -36,8 +43,60 @@ public class JavaSectionBlocksUpdateTranslator extends PacketTranslator Date: Fri, 17 Apr 2026 11:14:24 +0200 Subject: [PATCH 2/9] Update palette always --- .../JavaSectionBlocksUpdateTranslator.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java index 9c2652e8603..e78416f2154 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java @@ -38,6 +38,8 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; +import java.util.BitSet; + @Translator(packet = ClientboundSectionBlocksUpdatePacket.class) public class JavaSectionBlocksUpdateTranslator extends PacketTranslator { @@ -51,6 +53,8 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke } } + BitSet waterlogged = BlockRegistries.WATERLOGGED.get(); + UpdateSubChunkBlocksPacket updateSubChunkBlocksPacket = new UpdateSubChunkBlocksPacket(); for (BlockChangeEntry entry : packet.getEntries()) { @@ -58,18 +62,24 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke ? palette.get(entry.getPosition().getX() & 0xF, entry.getPosition().getY() & 0xF, entry.getPosition().getZ() & 0xF) : session.getGeyser().getWorldManager().getBlockAt(session, entry.getPosition()); if (entry.getBlock() == oldBlock) { + // Skip unchanged blocks which may occur with older versions of Minecraft continue; } + if (palette != null) { + palette.set(entry.getPosition().getX() & 0xF, entry.getPosition().getY() & 0xF, entry.getPosition().getZ() & 0xF, entry.getBlock()); + } + BlockState blockState = BlockState.of(entry.getBlock()); if (blockState.is(Blocks.AIR)) { ItemFrameEntity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, entry.getPosition()); - if (itemFrameEntity != null) { + if (itemFrameEntity != null) { // Item frame is still present and no block overrides that; refresh it itemFrameEntity.updateBlock(true); continue; } } if (!(blockState.block() instanceof SkullBlock)) { + // Skull is gone session.getSkullCache().removeSkull(entry.getPosition()); } @@ -81,8 +91,8 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry.MessageType.NONE )); - boolean isWaterlogged = BlockRegistries.WATERLOGGED.get().get(entry.getBlock()); - if (BlockRegistries.WATERLOGGED.get().get(oldBlock) != isWaterlogged) { + boolean isWaterlogged = waterlogged.get(entry.getBlock()); + if (waterlogged.get(oldBlock) != isWaterlogged) { updateSubChunkBlocksPacket.getExtraBlocks().add(new org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry( entry.getPosition(), isWaterlogged ? session.getBlockMappings().getBedrockWater() : session.getBlockMappings().getBedrockAir(), @@ -91,10 +101,6 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry.MessageType.NONE )); } - - if (palette != null) { - palette.set(entry.getPosition().getX() & 0xF, entry.getPosition().getY() & 0xF, entry.getPosition().getZ() & 0xF, entry.getBlock()); - } } session.sendUpstreamPacket(updateSubChunkBlocksPacket); From 638673a188cbbf5c732cf9518a6f7bc3f3f7e123 Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:39:56 +0200 Subject: [PATCH 3/9] Keep special handling of overwritten blocks --- .../level/JavaSectionBlocksUpdateTranslator.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java index e78416f2154..a75db2b7ce5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java @@ -28,6 +28,7 @@ import org.cloudburstmc.protocol.bedrock.packet.UpdateSubChunkBlocksPacket; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.level.block.Blocks; +import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.BlockState; import org.geysermc.geyser.level.block.type.SkullBlock; import org.geysermc.geyser.registry.BlockRegistries; @@ -78,11 +79,16 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke continue; } } - if (!(blockState.block() instanceof SkullBlock)) { - // Skull is gone - session.getSkullCache().removeSkull(entry.getPosition()); + + // Some block may have special handling, keep it that way + if (!(blockState.block().getClass().equals(Block.class))) { + blockState.block().updateBlock(session, blockState, entry.getPosition()); + continue; } + // Skull is gone + session.getSkullCache().removeSkull(entry.getPosition()); + updateSubChunkBlocksPacket.getStandardBlocks().add(new org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry( entry.getPosition(), session.getBlockMappings().getBedrockBlock(blockState), From ef4faf3ea3e30ab47dcf17d55250d23a8715e3cf Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:50:43 +0200 Subject: [PATCH 4/9] Only send non-empty packets --- .../java/level/JavaSectionBlocksUpdateTranslator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java index a75db2b7ce5..e638da56bd7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java @@ -30,7 +30,6 @@ import org.geysermc.geyser.level.block.Blocks; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.level.block.type.BlockState; -import org.geysermc.geyser.level.block.type.SkullBlock; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.mcprotocollib.protocol.data.game.chunk.DataPalette; import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockChangeEntry; @@ -109,6 +108,8 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke } } - session.sendUpstreamPacket(updateSubChunkBlocksPacket); + if (!updateSubChunkBlocksPacket.getStandardBlocks().isEmpty()) { + session.sendUpstreamPacket(updateSubChunkBlocksPacket); + } } } From 5a5c2c016000928ff0ca04a2de959ba17d5c3603 Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:57:43 +0200 Subject: [PATCH 5/9] Remove WaterBlock extend as it's not used --- .../geysermc/geyser/level/block/Blocks.java | 3 +- .../geyser/level/block/type/WaterBlock.java | 32 ------------------- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/level/block/type/WaterBlock.java diff --git a/core/src/main/java/org/geysermc/geyser/level/block/Blocks.java b/core/src/main/java/org/geysermc/geyser/level/block/Blocks.java index 8753287387e..fa3e6b6170a 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/Blocks.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/Blocks.java @@ -42,7 +42,6 @@ import org.geysermc.geyser.level.block.type.SkullBlock; import org.geysermc.geyser.level.block.type.TrapDoorBlock; import org.geysermc.geyser.level.block.type.WallSkullBlock; -import org.geysermc.geyser.level.block.type.WaterBlock; import org.geysermc.geyser.level.physics.Axis; import org.geysermc.geyser.level.physics.Direction; import org.geysermc.geyser.level.physics.PistonBehavior; @@ -104,7 +103,7 @@ public final class Blocks { .intState(STAGE) .booleanState(WATERLOGGED))); public static final Block BEDROCK = register(new Block("bedrock", builder().destroyTime(-1.0f))); - public static final Block WATER = register(new WaterBlock("water", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY) + public static final Block WATER = register(new Block("water", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY) .intState(LEVEL))); public static final Block LAVA = register(new Block("lava", builder().destroyTime(100.0f).pushReaction(PistonBehavior.DESTROY) .intState(LEVEL))); diff --git a/core/src/main/java/org/geysermc/geyser/level/block/type/WaterBlock.java b/core/src/main/java/org/geysermc/geyser/level/block/type/WaterBlock.java deleted file mode 100644 index 9d2d2311629..00000000000 --- a/core/src/main/java/org/geysermc/geyser/level/block/type/WaterBlock.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2024 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.level.block.type; - -public class WaterBlock extends Block { - public WaterBlock(String javaIdentifier, Builder builder) { - super(javaIdentifier, builder); - } -} From 6f3154af936cf645d3b8eb350e9c4161d2af39f2 Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:07:55 +0200 Subject: [PATCH 6/9] Make ChunkCache.getChunkSection universal --- .../geyser/session/cache/ChunkCache.java | 22 +++++-------------- .../JavaSectionBlocksUpdateTranslator.java | 2 +- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java index 1badfe60344..c6e78a33bb9 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/ChunkCache.java @@ -73,7 +73,7 @@ private GeyserChunk getChunk(int chunkX, int chunkZ) { * Doesn't check for cache enabled, so don't use this without checking that first! */ @Deprecated - public DataPalette getChunkSection(int chunkX, int chunkY, int chunkZ) { + public DataPalette getChunkSection(int chunkX, int chunkY, int chunkZ, boolean createIfAbsent) { GeyserChunk chunk = this.getChunk(chunkX, chunkZ); if (chunk == null) { return null; @@ -84,7 +84,7 @@ public DataPalette getChunkSection(int chunkX, int chunkY, int chunkZ) { } DataPalette palette = chunk.sections()[chunkY - getChunkMinY()]; - if (palette == null) { + if (createIfAbsent && palette == null) { palette = DataPalette.createForBlockState(Block.JAVA_AIR_ID, BlockRegistries.BLOCK_STATES.get().size()); chunk.sections()[chunkY - getChunkMinY()] = palette; } @@ -97,7 +97,7 @@ public void updateBlock(int x, int y, int z, int block) { return; } - DataPalette palette = this.getChunkSection(x >> 4, y >> 4, z >> 4); + DataPalette palette = this.getChunkSection(x >> 4, y >> 4, z >> 4, true); if (palette == null) { return; } @@ -110,22 +110,12 @@ public int getBlockAt(int x, int y, int z) { return Block.JAVA_AIR_ID; } - GeyserChunk column = this.getChunk(x >> 4, z >> 4); - if (column == null) { - return Block.JAVA_AIR_ID; - } - - if (y < minY || ((y - minY) >> 4) > column.sections().length - 1) { - // Y likely goes above or below the height limit of this world + DataPalette palette = this.getChunkSection(x >> 4, y >> 4, z >> 4, false); + if (palette == null) { return Block.JAVA_AIR_ID; } - DataPalette chunk = column.sections()[(y - minY) >> 4]; - if (chunk != null) { - return chunk.get(x & 0xF, y & 0xF, z & 0xF); - } - - return Block.JAVA_AIR_ID; + return palette.get(x & 0xF, y & 0xF, z & 0xF); } public void removeChunk(int chunkX, int chunkZ) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java index e638da56bd7..dcf117fc870 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java @@ -47,7 +47,7 @@ public class JavaSectionBlocksUpdateTranslator extends PacketTranslator Date: Fri, 17 Apr 2026 14:35:29 +0200 Subject: [PATCH 7/9] Move to constant, import MessageType directly --- .../java/level/JavaSectionBlocksUpdateTranslator.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java index dcf117fc870..b5e67f2d381 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java @@ -25,6 +25,8 @@ package org.geysermc.geyser.translator.protocol.java.level; +import org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry.MessageType; +import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateSubChunkBlocksPacket; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.level.block.Blocks; @@ -43,6 +45,8 @@ @Translator(packet = ClientboundSectionBlocksUpdatePacket.class) public class JavaSectionBlocksUpdateTranslator extends PacketTranslator { + private static final int FLAG_ALL = 1 << UpdateBlockPacket.Flag.NEIGHBORS.ordinal() | 1 << UpdateBlockPacket.Flag.NETWORK.ordinal(); + @Override public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacket packet) { DataPalette palette = null; @@ -91,9 +95,9 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke updateSubChunkBlocksPacket.getStandardBlocks().add(new org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry( entry.getPosition(), session.getBlockMappings().getBedrockBlock(blockState), - 3, + FLAG_ALL, -1, - org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry.MessageType.NONE + MessageType.NONE )); boolean isWaterlogged = waterlogged.get(entry.getBlock()); @@ -103,7 +107,7 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke isWaterlogged ? session.getBlockMappings().getBedrockWater() : session.getBlockMappings().getBedrockAir(), 0, -1, - org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry.MessageType.NONE + MessageType.NONE )); } } From a0f8ea08e6dbc14f7be2f723998dd05a35ba075c Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:29:24 +0200 Subject: [PATCH 8/9] behavioral identical to updateServerCorrectBlockState --- .../org/geysermc/geyser/session/cache/WorldCache.java | 8 +++++--- .../java/level/JavaSectionBlocksUpdateTranslator.java | 10 ++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index 070ae624985..e3ff2722f7e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -180,9 +180,7 @@ public void markPositionInSequence(Vector3i position) { } public void updateServerCorrectBlockState(Vector3i position, int blockState) { - if (!this.unverifiedPredictions.isEmpty()) { - this.unverifiedPredictions.removeInt(position); - } + this.unverifiedPredictions.removeInt(position); // Hack to avoid looking up blockstates for the currently broken position each tick Vector3i clientBreakPos = session.getBlockBreakHandler().getCurrentBlockPos(); @@ -193,6 +191,10 @@ public void updateServerCorrectBlockState(Vector3i position, int blockState) { ChunkUtils.updateBlock(session, blockState, position); } + public void removePrediction(Vector3i position) { + this.unverifiedPredictions.removeInt(position); + } + public void endPredictionsUpTo(int sequence) { if (this.unverifiedPredictions.isEmpty()) { return; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java index b5e67f2d381..587b786f5de 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSectionBlocksUpdateTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.translator.protocol.java.level; +import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.data.BlockChangeEntry.MessageType; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateSubChunkBlocksPacket; @@ -41,6 +42,7 @@ import org.geysermc.geyser.translator.protocol.Translator; import java.util.BitSet; +import java.util.Objects; @Translator(packet = ClientboundSectionBlocksUpdatePacket.class) public class JavaSectionBlocksUpdateTranslator extends PacketTranslator { @@ -57,11 +59,19 @@ public void translate(GeyserSession session, ClientboundSectionBlocksUpdatePacke } } + Vector3i clientBreakPos = session.getBlockBreakHandler().getCurrentBlockPos(); BitSet waterlogged = BlockRegistries.WATERLOGGED.get(); UpdateSubChunkBlocksPacket updateSubChunkBlocksPacket = new UpdateSubChunkBlocksPacket(); for (BlockChangeEntry entry : packet.getEntries()) { + session.getWorldCache().removePrediction(entry.getPosition()); + + // Hack to avoid looking up blockstates for the currently broken position each tick + if (clientBreakPos != null && Objects.equals(clientBreakPos, entry.getPosition())) { + session.getBlockBreakHandler().setUpdatedServerBlockStateId(entry.getBlock()); + } + int oldBlock = palette != null ? palette.get(entry.getPosition().getX() & 0xF, entry.getPosition().getY() & 0xF, entry.getPosition().getZ() & 0xF) : session.getGeyser().getWorldManager().getBlockAt(session, entry.getPosition()); From 346178c77d0e63bfd2053f5d0a222bcd27575aaf Mon Sep 17 00:00:00 2001 From: Valaphee <32491319+valaphee@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:38:22 +0200 Subject: [PATCH 9/9] comment updateBlock --- .../main/java/org/geysermc/geyser/level/block/type/Block.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java b/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java index 0863aa8138e..a619ec60e52 100644 --- a/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java +++ b/core/src/main/java/org/geysermc/geyser/level/block/type/Block.java @@ -86,6 +86,8 @@ public Block(@Subst("empty") String javaIdentifier, Builder builder) { this.defaultState = setDefaultState(firstState); } + // JavaSectionBlocksUpdateTranslator only calls updateBlock for specialized blocks, + // make sure to also update JavaSectionBlocksUpdateTranslator for general changes. public void updateBlock(GeyserSession session, BlockState state, Vector3i position) { checkForEmptySkull(session, state, position);