diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/Protocol1_19To1_18_2.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/Protocol1_19To1_18_2.java index 048f4f3e..ed01f5e1 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/Protocol1_19To1_18_2.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/Protocol1_19To1_18_2.java @@ -27,6 +27,7 @@ import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.BlockItemPacketRewriter1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.CommandRewriter1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.EntityPacketRewriter1_19; +import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.BlockAckStorage; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.DimensionRegistryStorage; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.EntityTracker1_19; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.NonceStorage; @@ -343,6 +344,7 @@ public void register() { @Override public void init(final UserConnection user) { + user.put(new BlockAckStorage()); user.put(new DimensionRegistryStorage()); addEntityTracker(user, new EntityTracker1_19(user)); } diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/BlockItemPacketRewriter1_19.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/BlockItemPacketRewriter1_19.java index f4182440..c55a026e 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/BlockItemPacketRewriter1_19.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/BlockItemPacketRewriter1_19.java @@ -21,10 +21,13 @@ import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter; import com.viaversion.viabackwards.api.rewriters.EnchantmentRewriter; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2; +import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.BlockAckStorage; import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.LastDeathPosition; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.data.ParticleMappings; import com.viaversion.viaversion.api.data.entity.EntityTracker; +import com.viaversion.viaversion.api.minecraft.BlockChangeRecord; +import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition; import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; @@ -36,6 +39,7 @@ import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18; import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17; +import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18; import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ClientboundPackets1_19; import com.viaversion.viaversion.rewriter.RecipeRewriter; import com.viaversion.viaversion.util.MathUtil; @@ -84,14 +88,85 @@ public void register() { } }); - protocol.registerClientbound(ClientboundPackets1_19.BLOCK_CHANGED_ACK, null, new PacketHandlers() { + protocol.registerClientbound(ClientboundPackets1_19.BLOCK_CHANGED_ACK, ClientboundPackets1_18.BLOCK_UPDATE, new PacketHandlers() { @Override public void register() { read(Types.VAR_INT); // Sequence - handler(PacketWrapper::cancel); // This is fine:tm: + handler(wrapper -> { + final BlockAckStorage storage = wrapper.user().get(BlockAckStorage.class); + final BlockPosition pos = storage.pollPendingBlock(); + if (pos == null) { + wrapper.cancel(); + return; + } + + final int minSectionY = protocol.getEntityRewriter().tracker(wrapper.user()).currentMinY() >> 4; + final int blockState = storage.getBlockStateAt(pos, minSectionY); + if (blockState < 0) { + wrapper.cancel(); + return; + } + + wrapper.write(Types.BLOCK_POSITION1_14, pos); + wrapper.write(Types.VAR_INT, blockState); + }); + } + }); + + protocol.replaceClientbound(ClientboundPackets1_19.BLOCK_UPDATE, new PacketHandlers() { + @Override + public void register() { + handler(wrapper -> { + final BlockPosition pos = wrapper.passthrough(Types.BLOCK_POSITION1_14); + final int blockId = wrapper.read(Types.VAR_INT); + final int mappedId = protocol.getMappingData().getNewBlockStateId(blockId); + wrapper.write(Types.VAR_INT, mappedId); + final int minSectionY = protocol.getEntityRewriter().tracker(wrapper.user()).currentMinY() >> 4; + wrapper.user().get(BlockAckStorage.class).updateBlockState(pos.x(), pos.y(), pos.z(), minSectionY, mappedId); + }); + } + }); + + protocol.replaceClientbound(ClientboundPackets1_19.SECTION_BLOCKS_UPDATE, new PacketHandlers() { + @Override + public void register() { + handler(wrapper -> { + final long sectionPos = wrapper.passthrough(Types.LONG); + wrapper.passthrough(Types.BOOLEAN); // Suppress light updates + + final int sectionY = (int) ((sectionPos << 44) >> 44); + final int chunkZ = (int) ((sectionPos << 22) >> 42); + final int chunkX = (int) (sectionPos >> 42); + + final int minSectionY = protocol.getEntityRewriter().tracker(wrapper.user()).currentMinY() >> 4; + final BlockAckStorage storage = wrapper.user().get(BlockAckStorage.class); + for (final BlockChangeRecord record : wrapper.passthrough(Types.VAR_LONG_BLOCK_CHANGE_ARRAY)) { + final int mappedId = protocol.getMappingData().getNewBlockStateId(record.getBlockId()); + record.setBlockId(mappedId); + storage.updateBlockState( + (chunkX << 4) | record.getSectionX(), + record.getY(sectionY), + (chunkZ << 4) | record.getSectionZ(), + minSectionY, + mappedId + ); + } + }); } }); + protocol.replaceClientbound(ClientboundPackets1_19.LEVEL_CHUNK_WITH_LIGHT, wrapper -> { + final Chunk chunk = protocol.getBlockRewriter().handleChunk1_18(wrapper); + protocol.getBlockRewriter().handleBlockEntities(chunk, wrapper.user()); + wrapper.user().get(BlockAckStorage.class).cacheChunk(chunk.getX(), chunk.getZ(), chunk.getSections()); + }); + + protocol.registerClientbound(ClientboundPackets1_19.FORGET_LEVEL_CHUNK, wrapper -> { + final int chunkX = wrapper.passthrough(Types.INT); + final int chunkZ = wrapper.passthrough(Types.INT); + wrapper.user().get(BlockAckStorage.class).forgetChunk(chunkX, chunkZ); + }); + protocol.replaceClientbound(ClientboundPackets1_19.LEVEL_PARTICLES, new PacketHandlers() { @Override public void register() { @@ -126,14 +201,18 @@ public void register() { } }); - // The server does nothing but track the sequence, so we can just set it as 0 protocol.registerServerbound(ServerboundPackets1_17.PLAYER_ACTION, new PacketHandlers() { @Override public void register() { - map(Types.VAR_INT); // Action - map(Types.BLOCK_POSITION1_14); // Block position - map(Types.UNSIGNED_BYTE); // Direction - create(Types.VAR_INT, 0); // Sequence + handler(wrapper -> { + final int action = wrapper.passthrough(Types.VAR_INT); + final BlockPosition pos = wrapper.passthrough(Types.BLOCK_POSITION1_14); + wrapper.passthrough(Types.UNSIGNED_BYTE); // Direction + wrapper.write(Types.VAR_INT, 0); // Sequence + if (action < 3) { + wrapper.user().get(BlockAckStorage.class).addPendingBlock(pos); + } + }); } }); protocol.registerServerbound(ServerboundPackets1_17.USE_ITEM_ON, new PacketHandlers() { diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/storage/BlockAckStorage.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/storage/BlockAckStorage.java new file mode 100644 index 00000000..771b34f7 --- /dev/null +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/storage/BlockAckStorage.java @@ -0,0 +1,109 @@ +/* + * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards + * Copyright (C) 2016-2026 ViaVersion and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage; + +import com.viaversion.viaversion.api.connection.StorableObject; +import com.viaversion.viaversion.api.minecraft.BlockPosition; +import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; +import com.viaversion.viaversion.api.minecraft.chunks.DataPalette; +import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import org.checkerframework.checker.nullness.qual.Nullable; + +public final class BlockAckStorage implements StorableObject { + + private final Queue pendingBlocks = new LinkedList<>(); + private final Map cachedChunks = new HashMap<>(); + + public void addPendingBlock(final BlockPosition pos) { + pendingBlocks.add(pos); + if (pendingBlocks.size() > 100) { + pendingBlocks.poll(); + } + } + + public @Nullable BlockPosition pollPendingBlock() { + return pendingBlocks.poll(); + } + + public void cacheChunk(final int chunkX, final int chunkZ, final ChunkSection[] sections) { + final int[][] blockStates = new int[sections.length][]; + for (int i = 0; i < sections.length; i++) { + final ChunkSection section = sections[i]; + if (section == null) { + continue; + } + final DataPalette palette = section.palette(PaletteType.BLOCKS); + final int[] states = new int[4096]; + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + states[x | (z << 4) | (y << 8)] = palette.idAt(x, y, z); + } + } + } + blockStates[i] = states; + } + cachedChunks.put(packChunk(chunkX, chunkZ), blockStates); + } + + public void forgetChunk(final int chunkX, final int chunkZ) { + cachedChunks.remove(packChunk(chunkX, chunkZ)); + } + + public void updateBlockState(final int blockX, final int blockY, final int blockZ, final int minSectionY, final int state) { + final int[][] sections = cachedChunks.get(packChunk(blockX >> 4, blockZ >> 4)); + if (sections == null) { + return; + } + final int sectionIdx = (blockY >> 4) - minSectionY; + if (sectionIdx < 0 || sectionIdx >= sections.length) { + return; + } + int[] section = sections[sectionIdx]; + if (section == null) { + section = new int[4096]; + sections[sectionIdx] = section; + } + section[(blockX & 15) | ((blockZ & 15) << 4) | ((blockY & 15) << 8)] = state; + } + + public int getBlockStateAt(final BlockPosition pos, final int minSectionY) { + final int[][] sections = cachedChunks.get(packChunk(pos.x() >> 4, pos.z() >> 4)); + if (sections == null) { + return -1; + } + final int sectionIdx = (pos.y() >> 4) - minSectionY; + if (sectionIdx < 0 || sectionIdx >= sections.length) { + return -1; + } + final int[] section = sections[sectionIdx]; + if (section == null) { + return -1; + } + return section[(pos.x() & 15) | ((pos.z() & 15) << 4) | ((pos.y() & 15) << 8)]; + } + + private static long packChunk(final int x, final int z) { + return ((long) x << 32) | (z & 0xFFFFFFFFL); + } + +}