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,88 @@ 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 blockAckStorage = wrapper.user().get(BlockAckStorage.class);
final int sequence = wrapper.read(Types.VAR_INT);
final BlockPosition pos = blockAckStorage.remove(sequence);
if (pos == null) {
wrapper.cancel();
return;
}

final int minSectionY = protocol.getEntityRewriter().tracker(wrapper.user()).currentMinY() >> 4;
final int blockState = blockAckStorage.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 BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
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;
blockAckStorage.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 blockAckStorage = 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);
blockAckStorage.updateBlockState(
(chunkX << 4) | record.getSectionX(),
(sectionY << 4) + record.getSectionY(),
(chunkZ << 4) | record.getSectionZ(),
minSectionY,
mappedId
);
}
});
}
});

protocol.replaceClientbound(ClientboundPackets1_19.LEVEL_CHUNK_WITH_LIGHT, wrapper -> {
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
final Chunk chunk = protocol.getBlockRewriter().handleChunk1_18(wrapper);
protocol.getBlockRewriter().handleBlockEntities(chunk, wrapper.user());
blockAckStorage.cacheChunk(chunk.getX(), chunk.getZ(), chunk.getSections());
});

protocol.registerClientbound(ClientboundPackets1_19.FORGET_LEVEL_CHUNK, wrapper -> {
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
final int chunkX = wrapper.passthrough(Types.INT);
final int chunkZ = wrapper.passthrough(Types.INT);
blockAckStorage.forgetChunk(chunkX, chunkZ);
});

protocol.replaceClientbound(ClientboundPackets1_19.LEVEL_PARTICLES, new PacketHandlers() {
@Override
public void register() {
Expand Down Expand Up @@ -126,34 +204,46 @@ 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 BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
final int action = wrapper.passthrough(Types.VAR_INT);
final BlockPosition pos = wrapper.passthrough(Types.BLOCK_POSITION1_14);
wrapper.passthrough(Types.UNSIGNED_BYTE); // Direction
final int sequence = blockAckStorage.nextSequence();
wrapper.write(Types.VAR_INT, sequence); // Sequence
if (action < 3) {
blockAckStorage.add(sequence, pos);
}
});
}
});
protocol.registerServerbound(ServerboundPackets1_17.USE_ITEM_ON, new PacketHandlers() {
@Override
public void register() {
map(Types.VAR_INT); // Hand
map(Types.BLOCK_POSITION1_14); // Block position
map(Types.VAR_INT); // Direction
map(Types.FLOAT); // X
map(Types.FLOAT); // Y
map(Types.FLOAT); // Z
map(Types.BOOLEAN); // Inside
create(Types.VAR_INT, 0); // Sequence
handler(wrapper -> {
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
wrapper.passthrough(Types.VAR_INT); // Hand
wrapper.passthrough(Types.BLOCK_POSITION1_14); // Block position
wrapper.passthrough(Types.VAR_INT); // Direction
wrapper.passthrough(Types.FLOAT); // X
wrapper.passthrough(Types.FLOAT); // Y
wrapper.passthrough(Types.FLOAT); // Z
wrapper.passthrough(Types.BOOLEAN); // Inside
wrapper.write(Types.VAR_INT, blockAckStorage.nextSequence()); // Sequence
});
}
});
protocol.registerServerbound(ServerboundPackets1_17.USE_ITEM, new PacketHandlers() {
@Override
public void register() {
map(Types.VAR_INT); // Hand
create(Types.VAR_INT, 0); // Sequence
handler(wrapper -> {
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
wrapper.passthrough(Types.VAR_INT); // Hand
wrapper.write(Types.VAR_INT, blockAckStorage.nextSequence()); // Sequence
});
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.viaversion.viabackwards.ViaBackwards;
import com.viaversion.viabackwards.api.rewriters.EntityRewriter;
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.DimensionRegistryStorage;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.EntityTracker1_19;
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.LastDeathPosition;
Expand Down Expand Up @@ -206,6 +207,8 @@ public void register() {
map(Types.STRING_ARRAY); // Worlds
map(Types.NAMED_COMPOUND_TAG); // Dimension registry
handler(wrapper -> {
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
blockAckStorage.clear();
final DimensionRegistryStorage dimensionRegistryStorage = wrapper.user().get(DimensionRegistryStorage.class);
dimensionRegistryStorage.clear();

Expand Down Expand Up @@ -269,6 +272,7 @@ public void register() {
@Override
public void register() {
handler(wrapper -> {
wrapper.user().get(BlockAckStorage.class).clear();
final String dimensionKey = wrapper.read(Types.STRING);
final CompoundTag dimension = wrapper.user().get(DimensionRegistryStorage.class).dimension(dimensionKey);
if (dimension == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.ChunkPosition;
import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection;
import com.viaversion.viaversion.api.minecraft.chunks.DataPalette;
import com.viaversion.viaversion.api.minecraft.chunks.DataPaletteImpl;
import com.viaversion.viaversion.api.minecraft.chunks.PaletteType;
import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;

public final class BlockAckStorage implements StorableObject {

private final Int2ObjectLinkedOpenHashMap<BlockPosition> blockPosition = new Int2ObjectLinkedOpenHashMap<>();
private final Map<Long, DataPalette[]> cachedChunks = new HashMap<>();
private int sequenceId;

public int nextSequence() {
return sequenceId++;
}

public void add(final int sequence, final BlockPosition position) {
blockPosition.put(sequence, position);

// Some actions may be left unacknowledged by modded servers
if (blockPosition.size() > 100) {
blockPosition.removeFirst();
}
}

public @Nullable BlockPosition remove(final int sequence) {
return blockPosition.remove(sequence);
}

public void cacheChunk(final int chunkX, final int chunkZ, final ChunkSection[] sections) {
final DataPalette[] blockPalettes = new DataPalette[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);
if (palette != null) {
blockPalettes[i] = copyPalette(palette);
}
}
cachedChunks.put(ChunkPosition.chunkKey(chunkX, chunkZ), blockPalettes);
}

public void forgetChunk(final int chunkX, final int chunkZ) {
cachedChunks.remove(ChunkPosition.chunkKey(chunkX, chunkZ));
}

public void clear() {
blockPosition.clear();
cachedChunks.clear();
sequenceId = 0;
}

public void updateBlockState(final int blockX, final int blockY, final int blockZ, final int minSectionY, final int state) {
final DataPalette[] sections = cachedChunks.get(ChunkPosition.chunkKeyForBlock(blockX, blockZ));
if (sections == null) {
return;
}
final int sectionIdx = (blockY >> 4) - minSectionY;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
return;
}
final DataPalette palette = sections[sectionIdx];
if (palette == null) {
return;
}
palette.setIdAt(blockX & 15, blockY & 15, blockZ & 15, state);
}

public int getBlockStateAt(final BlockPosition pos, final int minSectionY) {
final DataPalette[] sections = cachedChunks.get(ChunkPosition.chunkKeyForBlock(pos.x(), pos.z()));
if (sections == null) {
return -1;
}
final int sectionIdx = (pos.y() >> 4) - minSectionY;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
return -1;
}
final DataPalette palette = sections[sectionIdx];
if (palette == null) {
return -1;
}
return palette.idAt(pos.x() & 15, pos.y() & 15, pos.z() & 15);
}

private static DataPalette copyPalette(final DataPalette palette) {
final DataPaletteImpl copy = new DataPaletteImpl(ChunkSection.SIZE, palette.size());
for (int i = 0; i < palette.size(); i++) {
copy.addId(palette.idByIndex(i));
}
for (int i = 0; i < ChunkSection.SIZE; i++) {
copy.setPaletteIndexAt(i, palette.paletteIndexAt(i));
}
return copy;
}
}