Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<BlockPosition> pendingBlocks = new LinkedList<>();
private final Map<Long, int[][]> 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);
}

}