Skip to content

Commit a42b273

Browse files
committed
Map BLOCK_CHANGED_ACK to BLOCK_UPDATE in 1.19 -> 1.18.2
1 parent 7700afb commit a42b273

4 files changed

Lines changed: 237 additions & 18 deletions

File tree

common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/Protocol1_19To1_18_2.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.BlockItemPacketRewriter1_19;
2828
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.CommandRewriter1_19;
2929
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.rewriter.EntityPacketRewriter1_19;
30+
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.BlockAckStorage;
3031
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.DimensionRegistryStorage;
3132
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.EntityTracker1_19;
3233
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.NonceStorage;
@@ -343,6 +344,7 @@ public void register() {
343344

344345
@Override
345346
public void init(final UserConnection user) {
347+
user.put(new BlockAckStorage());
346348
user.put(new DimensionRegistryStorage());
347349
addEntityTracker(user, new EntityTracker1_19(user));
348350
}

common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/BlockItemPacketRewriter1_19.java

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121
import com.viaversion.viabackwards.api.rewriters.BackwardsItemRewriter;
2222
import com.viaversion.viabackwards.api.rewriters.EnchantmentRewriter;
2323
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2;
24+
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.BlockAckStorage;
2425
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.LastDeathPosition;
2526
import com.viaversion.viaversion.api.connection.UserConnection;
2627
import com.viaversion.viaversion.api.data.ParticleMappings;
2728
import com.viaversion.viaversion.api.data.entity.EntityTracker;
29+
import com.viaversion.viaversion.api.minecraft.BlockChangeRecord;
30+
import com.viaversion.viaversion.api.minecraft.BlockPosition;
2831
import com.viaversion.viaversion.api.minecraft.GlobalBlockPosition;
2932
import com.viaversion.viaversion.api.minecraft.chunks.Chunk;
3033
import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection;
@@ -36,6 +39,7 @@
3639
import com.viaversion.viaversion.api.type.Types;
3740
import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_18;
3841
import com.viaversion.viaversion.protocols.v1_16_4to1_17.packet.ServerboundPackets1_17;
42+
import com.viaversion.viaversion.protocols.v1_17_1to1_18.packet.ClientboundPackets1_18;
3943
import com.viaversion.viaversion.protocols.v1_18_2to1_19.packet.ClientboundPackets1_19;
4044
import com.viaversion.viaversion.rewriter.RecipeRewriter;
4145
import com.viaversion.viaversion.util.MathUtil;
@@ -84,14 +88,88 @@ public void register() {
8488
}
8589
});
8690

87-
protocol.registerClientbound(ClientboundPackets1_19.BLOCK_CHANGED_ACK, null, new PacketHandlers() {
91+
protocol.registerClientbound(ClientboundPackets1_19.BLOCK_CHANGED_ACK, ClientboundPackets1_18.BLOCK_UPDATE, new PacketHandlers() {
8892
@Override
8993
public void register() {
90-
read(Types.VAR_INT); // Sequence
91-
handler(PacketWrapper::cancel); // This is fine:tm:
94+
handler(wrapper -> {
95+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
96+
final int sequence = wrapper.read(Types.VAR_INT);
97+
final BlockPosition pos = blockAckStorage.remove(sequence);
98+
if (pos == null) {
99+
wrapper.cancel();
100+
return;
101+
}
102+
103+
final int minSectionY = protocol.getEntityRewriter().tracker(wrapper.user()).currentMinY() >> 4;
104+
final int blockState = blockAckStorage.getBlockStateAt(pos, minSectionY);
105+
if (blockState < 0) {
106+
wrapper.cancel();
107+
return;
108+
}
109+
110+
wrapper.write(Types.BLOCK_POSITION1_14, pos);
111+
wrapper.write(Types.VAR_INT, blockState);
112+
});
113+
}
114+
});
115+
116+
protocol.replaceClientbound(ClientboundPackets1_19.BLOCK_UPDATE, new PacketHandlers() {
117+
@Override
118+
public void register() {
119+
handler(wrapper -> {
120+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
121+
final BlockPosition pos = wrapper.passthrough(Types.BLOCK_POSITION1_14);
122+
final int blockId = wrapper.read(Types.VAR_INT);
123+
final int mappedId = protocol.getMappingData().getNewBlockStateId(blockId);
124+
wrapper.write(Types.VAR_INT, mappedId);
125+
final int minSectionY = protocol.getEntityRewriter().tracker(wrapper.user()).currentMinY() >> 4;
126+
blockAckStorage.updateBlockState(pos.x(), pos.y(), pos.z(), minSectionY, mappedId);
127+
});
128+
}
129+
});
130+
131+
protocol.replaceClientbound(ClientboundPackets1_19.SECTION_BLOCKS_UPDATE, new PacketHandlers() {
132+
@Override
133+
public void register() {
134+
handler(wrapper -> {
135+
final long sectionPos = wrapper.passthrough(Types.LONG);
136+
wrapper.passthrough(Types.BOOLEAN); // Suppress light updates
137+
138+
final int sectionY = (int) ((sectionPos << 44) >> 44);
139+
final int chunkZ = (int) ((sectionPos << 22) >> 42);
140+
final int chunkX = (int) (sectionPos >> 42);
141+
142+
final int minSectionY = protocol.getEntityRewriter().tracker(wrapper.user()).currentMinY() >> 4;
143+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
144+
for (final BlockChangeRecord record : wrapper.passthrough(Types.VAR_LONG_BLOCK_CHANGE_ARRAY)) {
145+
final int mappedId = protocol.getMappingData().getNewBlockStateId(record.getBlockId());
146+
record.setBlockId(mappedId);
147+
blockAckStorage.updateBlockState(
148+
(chunkX << 4) | record.getSectionX(),
149+
(sectionY << 4) + record.getSectionY(),
150+
(chunkZ << 4) | record.getSectionZ(),
151+
minSectionY,
152+
mappedId
153+
);
154+
}
155+
});
92156
}
93157
});
94158

159+
protocol.replaceClientbound(ClientboundPackets1_19.LEVEL_CHUNK_WITH_LIGHT, wrapper -> {
160+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
161+
final Chunk chunk = protocol.getBlockRewriter().handleChunk1_18(wrapper);
162+
protocol.getBlockRewriter().handleBlockEntities(chunk, wrapper.user());
163+
blockAckStorage.cacheChunk(chunk.getX(), chunk.getZ(), chunk.getSections());
164+
});
165+
166+
protocol.registerClientbound(ClientboundPackets1_19.FORGET_LEVEL_CHUNK, wrapper -> {
167+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
168+
final int chunkX = wrapper.passthrough(Types.INT);
169+
final int chunkZ = wrapper.passthrough(Types.INT);
170+
blockAckStorage.forgetChunk(chunkX, chunkZ);
171+
});
172+
95173
protocol.replaceClientbound(ClientboundPackets1_19.LEVEL_PARTICLES, new PacketHandlers() {
96174
@Override
97175
public void register() {
@@ -126,34 +204,46 @@ public void register() {
126204
}
127205
});
128206

129-
// The server does nothing but track the sequence, so we can just set it as 0
130207
protocol.registerServerbound(ServerboundPackets1_17.PLAYER_ACTION, new PacketHandlers() {
131208
@Override
132209
public void register() {
133-
map(Types.VAR_INT); // Action
134-
map(Types.BLOCK_POSITION1_14); // Block position
135-
map(Types.UNSIGNED_BYTE); // Direction
136-
create(Types.VAR_INT, 0); // Sequence
210+
handler(wrapper -> {
211+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
212+
final int action = wrapper.passthrough(Types.VAR_INT);
213+
final BlockPosition pos = wrapper.passthrough(Types.BLOCK_POSITION1_14);
214+
wrapper.passthrough(Types.UNSIGNED_BYTE); // Direction
215+
final int sequence = blockAckStorage.nextSequence();
216+
wrapper.write(Types.VAR_INT, sequence); // Sequence
217+
if (action < 3) {
218+
blockAckStorage.add(sequence, pos);
219+
}
220+
});
137221
}
138222
});
139223
protocol.registerServerbound(ServerboundPackets1_17.USE_ITEM_ON, new PacketHandlers() {
140224
@Override
141225
public void register() {
142-
map(Types.VAR_INT); // Hand
143-
map(Types.BLOCK_POSITION1_14); // Block position
144-
map(Types.VAR_INT); // Direction
145-
map(Types.FLOAT); // X
146-
map(Types.FLOAT); // Y
147-
map(Types.FLOAT); // Z
148-
map(Types.BOOLEAN); // Inside
149-
create(Types.VAR_INT, 0); // Sequence
226+
handler(wrapper -> {
227+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
228+
wrapper.passthrough(Types.VAR_INT); // Hand
229+
wrapper.passthrough(Types.BLOCK_POSITION1_14); // Block position
230+
wrapper.passthrough(Types.VAR_INT); // Direction
231+
wrapper.passthrough(Types.FLOAT); // X
232+
wrapper.passthrough(Types.FLOAT); // Y
233+
wrapper.passthrough(Types.FLOAT); // Z
234+
wrapper.passthrough(Types.BOOLEAN); // Inside
235+
wrapper.write(Types.VAR_INT, blockAckStorage.nextSequence()); // Sequence
236+
});
150237
}
151238
});
152239
protocol.registerServerbound(ServerboundPackets1_17.USE_ITEM, new PacketHandlers() {
153240
@Override
154241
public void register() {
155-
map(Types.VAR_INT); // Hand
156-
create(Types.VAR_INT, 0); // Sequence
242+
handler(wrapper -> {
243+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
244+
wrapper.passthrough(Types.VAR_INT); // Hand
245+
wrapper.write(Types.VAR_INT, blockAckStorage.nextSequence()); // Sequence
246+
});
157247
}
158248
});
159249

common/src/main/java/com/viaversion/viabackwards/protocol/v1_19to1_18_2/rewriter/EntityPacketRewriter1_19.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.viaversion.viabackwards.ViaBackwards;
2424
import com.viaversion.viabackwards.api.rewriters.EntityRewriter;
2525
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.Protocol1_19To1_18_2;
26+
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.BlockAckStorage;
2627
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.DimensionRegistryStorage;
2728
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.EntityTracker1_19;
2829
import com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage.LastDeathPosition;
@@ -206,6 +207,8 @@ public void register() {
206207
map(Types.STRING_ARRAY); // Worlds
207208
map(Types.NAMED_COMPOUND_TAG); // Dimension registry
208209
handler(wrapper -> {
210+
final BlockAckStorage blockAckStorage = wrapper.user().get(BlockAckStorage.class);
211+
blockAckStorage.clear();
209212
final DimensionRegistryStorage dimensionRegistryStorage = wrapper.user().get(DimensionRegistryStorage.class);
210213
dimensionRegistryStorage.clear();
211214

@@ -269,6 +272,7 @@ public void register() {
269272
@Override
270273
public void register() {
271274
handler(wrapper -> {
275+
wrapper.user().get(BlockAckStorage.class).clear();
272276
final String dimensionKey = wrapper.read(Types.STRING);
273277
final CompoundTag dimension = wrapper.user().get(DimensionRegistryStorage.class).dimension(dimensionKey);
274278
if (dimension == null) {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards
3+
* Copyright (C) 2016-2026 ViaVersion and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.viaversion.viabackwards.protocol.v1_19to1_18_2.storage;
19+
20+
import com.viaversion.viaversion.api.connection.StorableObject;
21+
import com.viaversion.viaversion.api.minecraft.BlockPosition;
22+
import com.viaversion.viaversion.api.minecraft.ChunkPosition;
23+
import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection;
24+
import com.viaversion.viaversion.api.minecraft.chunks.DataPalette;
25+
import com.viaversion.viaversion.api.minecraft.chunks.DataPaletteImpl;
26+
import com.viaversion.viaversion.api.minecraft.chunks.PaletteType;
27+
import com.viaversion.viaversion.libs.fastutil.ints.Int2ObjectLinkedOpenHashMap;
28+
import java.util.HashMap;
29+
import java.util.Map;
30+
import org.checkerframework.checker.nullness.qual.Nullable;
31+
32+
public final class BlockAckStorage implements StorableObject {
33+
34+
private final Int2ObjectLinkedOpenHashMap<BlockPosition> blockPosition = new Int2ObjectLinkedOpenHashMap<>();
35+
private final Map<Long, DataPalette[]> cachedChunks = new HashMap<>();
36+
private int sequenceId;
37+
38+
public int nextSequence() {
39+
return sequenceId++;
40+
}
41+
42+
public void add(final int sequence, final BlockPosition position) {
43+
blockPosition.put(sequence, position);
44+
45+
// Some actions may be left unacknowledged by modded servers
46+
if (blockPosition.size() > 100) {
47+
blockPosition.removeFirst();
48+
}
49+
}
50+
51+
public @Nullable BlockPosition remove(final int sequence) {
52+
return blockPosition.remove(sequence);
53+
}
54+
55+
public void cacheChunk(final int chunkX, final int chunkZ, final ChunkSection[] sections) {
56+
final DataPalette[] blockPalettes = new DataPalette[sections.length];
57+
for (int i = 0; i < sections.length; i++) {
58+
final ChunkSection section = sections[i];
59+
if (section == null) {
60+
continue;
61+
}
62+
63+
final DataPalette palette = section.palette(PaletteType.BLOCKS);
64+
if (palette != null) {
65+
blockPalettes[i] = copyPalette(palette);
66+
}
67+
}
68+
cachedChunks.put(ChunkPosition.chunkKey(chunkX, chunkZ), blockPalettes);
69+
}
70+
71+
public void forgetChunk(final int chunkX, final int chunkZ) {
72+
cachedChunks.remove(ChunkPosition.chunkKey(chunkX, chunkZ));
73+
}
74+
75+
public void clear() {
76+
blockPosition.clear();
77+
cachedChunks.clear();
78+
sequenceId = 0;
79+
}
80+
81+
public void updateBlockState(final int blockX, final int blockY, final int blockZ, final int minSectionY, final int state) {
82+
final DataPalette[] sections = cachedChunks.get(ChunkPosition.chunkKeyForBlock(blockX, blockZ));
83+
if (sections == null) {
84+
return;
85+
}
86+
final int sectionIdx = (blockY >> 4) - minSectionY;
87+
if (sectionIdx < 0 || sectionIdx >= sections.length) {
88+
return;
89+
}
90+
final DataPalette palette = sections[sectionIdx];
91+
if (palette == null) {
92+
return;
93+
}
94+
palette.setIdAt(blockX & 15, blockY & 15, blockZ & 15, state);
95+
}
96+
97+
public int getBlockStateAt(final BlockPosition pos, final int minSectionY) {
98+
final DataPalette[] sections = cachedChunks.get(ChunkPosition.chunkKeyForBlock(pos.x(), pos.z()));
99+
if (sections == null) {
100+
return -1;
101+
}
102+
final int sectionIdx = (pos.y() >> 4) - minSectionY;
103+
if (sectionIdx < 0 || sectionIdx >= sections.length) {
104+
return -1;
105+
}
106+
final DataPalette palette = sections[sectionIdx];
107+
if (palette == null) {
108+
return -1;
109+
}
110+
return palette.idAt(pos.x() & 15, pos.y() & 15, pos.z() & 15);
111+
}
112+
113+
private static DataPalette copyPalette(final DataPalette palette) {
114+
final DataPaletteImpl copy = new DataPaletteImpl(ChunkSection.SIZE, palette.size());
115+
for (int i = 0; i < palette.size(); i++) {
116+
copy.addId(palette.idByIndex(i));
117+
}
118+
for (int i = 0; i < ChunkSection.SIZE; i++) {
119+
copy.setPaletteIndexAt(i, palette.paletteIndexAt(i));
120+
}
121+
return copy;
122+
}
123+
}

0 commit comments

Comments
 (0)