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