From e7a0b4cf049160fbeb3655f591306e7f6b716448 Mon Sep 17 00:00:00 2001 From: RK_01 <50594595+RaphiMC@users.noreply.github.com> Date: Sat, 28 Aug 2021 10:02:27 +0200 Subject: [PATCH] Improve 1.8 -> 1.9 chunk translation and fix #2065 (#2669) --- .../api/configuration/ViaVersionConfig.java | 9 +- .../viaversion/api/minecraft/BlockFace.java | 2 + .../types/minecraft/BaseChunkBulkType.java} | 40 ++-- .../types/version/ChunkSectionType1_8.java | 11 +- .../configuration/AbstractViaConfig.java | 7 + .../Protocol1_10To1_9_3_4.java | 70 ++++++- .../Protocol1_9_3To1_9_1_2.java | 4 +- .../types/Chunk1_9_1_2Type.java | 8 - .../protocol1_9to1_8/Protocol1_9To1_8.java | 17 +- .../packets/PlayerPackets.java | 43 ++-- .../packets/WorldPackets.java | 117 ++++++----- .../storage/ClientChunks.java | 6 +- .../protocol1_9to1_8/types/Chunk1_8Type.java | 148 ++++++++++++++ .../types/Chunk1_9to1_8Type.java | 185 ------------------ .../types/ChunkBulk1_8Type.java | 127 ++++++++++++ .../viaversion/rewriter/EntityRewriter.java | 2 +- .../resources/assets/viaversion/config.yml | 2 + 17 files changed, 483 insertions(+), 315 deletions(-) rename api/src/main/java/com/viaversion/viaversion/api/{minecraft/chunks/Chunk1_8.java => type/types/minecraft/BaseChunkBulkType.java} (56%) create mode 100644 common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_8Type.java delete mode 100644 common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_9to1_8Type.java create mode 100644 common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/ChunkBulk1_8Type.java diff --git a/api/src/main/java/com/viaversion/viaversion/api/configuration/ViaVersionConfig.java b/api/src/main/java/com/viaversion/viaversion/api/configuration/ViaVersionConfig.java index f4f2a5668..7199105b1 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/configuration/ViaVersionConfig.java +++ b/api/src/main/java/com/viaversion/viaversion/api/configuration/ViaVersionConfig.java @@ -230,6 +230,13 @@ public interface ViaVersionConfig { */ int getPistonReplacementId(); + /** + * Fix 1.9+ clients not rendering the far away chunks + * + * @return true to fix chunk borders + */ + boolean isChunkBorderFix(); + /** * Force json transform * @@ -417,7 +424,7 @@ public interface ViaVersionConfig { boolean isForcedUse1_17ResourcePack(); /** - * Get the message that is sent when a user displays a resource pack prompt. + * Get the message that is sent when a user displays a resource pack prompt. * * @return cached serialized component */ diff --git a/api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockFace.java b/api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockFace.java index 2e286dacd..96539fd3f 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockFace.java +++ b/api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockFace.java @@ -33,6 +33,8 @@ public enum BlockFace { TOP((byte) 0, (byte) 1, (byte) 0, EnumAxis.Y), BOTTOM((byte) 0, (byte) -1, (byte) 0, EnumAxis.Y); + public static final BlockFace[] HORIZONTAL = new BlockFace[]{NORTH, SOUTH, EAST, WEST}; + private static final Map opposites = new HashMap<>(); static { diff --git a/api/src/main/java/com/viaversion/viaversion/api/minecraft/chunks/Chunk1_8.java b/api/src/main/java/com/viaversion/viaversion/api/type/types/minecraft/BaseChunkBulkType.java similarity index 56% rename from api/src/main/java/com/viaversion/viaversion/api/minecraft/chunks/Chunk1_8.java rename to api/src/main/java/com/viaversion/viaversion/api/type/types/minecraft/BaseChunkBulkType.java index a1840c978..416639649 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/minecraft/chunks/Chunk1_8.java +++ b/api/src/main/java/com/viaversion/viaversion/api/type/types/minecraft/BaseChunkBulkType.java @@ -20,41 +20,23 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package com.viaversion.viaversion.api.minecraft.chunks; +package com.viaversion.viaversion.api.type.types.minecraft; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.viaversion.viaversion.api.minecraft.chunks.Chunk; +import com.viaversion.viaversion.api.type.Type; -import java.util.ArrayList; -import java.util.List; +public abstract class BaseChunkBulkType extends Type { -public class Chunk1_8 extends BaseChunk { - private boolean unloadPacket; - - public Chunk1_8(int x, int z, boolean groundUp, int bitmask, ChunkSection[] sections, int[] biomeData, List blockEntities) { - super(x, z, groundUp, false, bitmask, sections, biomeData, blockEntities); + protected BaseChunkBulkType() { + super(Chunk[].class); } - /** - * Chunk unload. - * - * @param x coord - * @param z coord - */ - public Chunk1_8(int x, int z) { - this(x, z, true, 0, new ChunkSection[16], null, new ArrayList<>()); - this.unloadPacket = true; + protected BaseChunkBulkType(String typeName) { + super(typeName, Chunk[].class); } - /** - * Does this chunks have biome data - * - * @return True if the chunks has biome data - */ - public boolean hasBiomeData() { - return biomeData != null && fullChunk; - } - - public boolean isUnloadPacket() { - return unloadPacket; + @Override + public Class getBaseClass() { + return BaseChunkBulkType.class; } } diff --git a/api/src/main/java/com/viaversion/viaversion/api/type/types/version/ChunkSectionType1_8.java b/api/src/main/java/com/viaversion/viaversion/api/type/types/version/ChunkSectionType1_8.java index b5c7fde67..b480cede1 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/type/types/version/ChunkSectionType1_8.java +++ b/api/src/main/java/com/viaversion/viaversion/api/type/types/version/ChunkSectionType1_8.java @@ -55,6 +55,15 @@ public class ChunkSectionType1_8 extends Type { @Override public void write(ByteBuf buffer, ChunkSection chunkSection) throws Exception { - throw new UnsupportedOperationException(); + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + int block = chunkSection.getFlatBlock(x, y, z); + buffer.writeByte(block); + buffer.writeByte(block >> 8); + } + } + } } + } diff --git a/common/src/main/java/com/viaversion/viaversion/configuration/AbstractViaConfig.java b/common/src/main/java/com/viaversion/viaversion/configuration/AbstractViaConfig.java index f4a76d02a..8a6415679 100644 --- a/common/src/main/java/com/viaversion/viaversion/configuration/AbstractViaConfig.java +++ b/common/src/main/java/com/viaversion/viaversion/configuration/AbstractViaConfig.java @@ -52,6 +52,7 @@ public abstract class AbstractViaConfig extends Config implements ViaVersionConf private boolean nmsPlayerTicking; private boolean replacePistons; private int pistonReplacementId; + private boolean chunkBorderFix; private boolean autoTeam; private boolean forceJsonTransform; private boolean nbtArrayFix; @@ -114,6 +115,7 @@ public abstract class AbstractViaConfig extends Config implements ViaVersionConf nmsPlayerTicking = getBoolean("nms-player-ticking", true); replacePistons = getBoolean("replace-pistons", false); pistonReplacementId = getInt("replacement-piston-id", 0); + chunkBorderFix = getBoolean("chunk-border-fix", false); autoTeam = getBoolean("auto-team", true); forceJsonTransform = getBoolean("force-json-transform", false); nbtArrayFix = getBoolean("chat-nbt-fix", true); @@ -277,6 +279,11 @@ public abstract class AbstractViaConfig extends Config implements ViaVersionConf return pistonReplacementId; } + @Override + public boolean isChunkBorderFix() { + return chunkBorderFix; + } + @Override public boolean isAutoTeam() { // Collision has to be enabled first diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_10to1_9_3/Protocol1_10To1_9_3_4.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_10to1_9_3/Protocol1_10To1_9_3_4.java index 1fc1391e7..04c8c7f84 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_10to1_9_3/Protocol1_10To1_9_3_4.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_10to1_9_3/Protocol1_10To1_9_3_4.java @@ -17,11 +17,13 @@ */ package com.viaversion.viaversion.protocols.protocol1_10to1_9_3; +import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.chunks.Chunk; +import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.metadata.Metadata; import com.viaversion.viaversion.api.protocol.AbstractProtocol; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; -import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketRemapper; import com.viaversion.viaversion.api.protocol.remapper.ValueTransformer; @@ -30,8 +32,10 @@ import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.types.version.Types1_9; import com.viaversion.viaversion.protocols.protocol1_10to1_9_3.packets.InventoryPackets; import com.viaversion.viaversion.protocols.protocol1_10to1_9_3.storage.ResourcePackTracker; +import com.viaversion.viaversion.protocols.protocol1_9_1_2to1_9_3_4.types.Chunk1_9_3_4Type; import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.ClientboundPackets1_9_3; import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.ServerboundPackets1_9_3; +import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.storage.ClientWorld; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -145,6 +149,66 @@ public class Protocol1_10To1_9_3_4 extends AbstractProtocol { @Override public Chunk read(ByteBuf input, ClientWorld world) throws Exception { - boolean replacePistons = world.getUser().getProtocolInfo().getPipeline().contains(Protocol1_10To1_9_3_4.class) && Via.getConfig().isReplacePistons(); - int replacementId = Via.getConfig().getPistonReplacementId(); - int chunkX = input.readInt(); int chunkZ = input.readInt(); @@ -71,9 +66,6 @@ public class Chunk1_9_1_2Type extends PartialType { if (world.getEnvironment() == Environment.NORMAL) { section.getLight().readSkyLight(input); } - if (replacePistons) { - section.replacePaletteEntry(36, replacementId); - } } int[] biomeData = groundUp ? new int[256] : null; diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/Protocol1_9To1_8.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/Protocol1_9To1_8.java index 644c8a47a..0c465f93d 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/Protocol1_9To1_8.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/Protocol1_9To1_8.java @@ -32,18 +32,11 @@ import com.viaversion.viaversion.api.rewriter.EntityRewriter; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.protocols.protocol1_8.ClientboundPackets1_8; import com.viaversion.viaversion.protocols.protocol1_8.ServerboundPackets1_8; +import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.storage.ClientWorld; import com.viaversion.viaversion.protocols.protocol1_9to1_8.metadata.MetadataRewriter1_9To1_8; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.packets.EntityPackets; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.packets.InventoryPackets; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.packets.PlayerPackets; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.packets.SpawnPackets; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.packets.WorldPackets; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.packets.*; import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.*; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.ClientChunks; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.CommandBlockStorage; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.EntityTracker1_9; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.InventoryTracker; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.MovementTracker; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.*; import com.viaversion.viaversion.util.GsonUtil; public class Protocol1_9To1_8 extends AbstractProtocol { @@ -152,6 +145,10 @@ public class Protocol1_9To1_8 extends AbstractProtocol { + ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); + int dimensionId = wrapper.get(Type.BYTE, 0); + clientWorld.setEnvironment(dimensionId); + }); + // Gotta fake their op handler(wrapper -> { CommandBlockProvider provider = Via.getManager().getProviders().get(CommandBlockProvider.class); provider.sendPermission(wrapper.user()); - } - ); + }); // Scoreboard will be cleared when join game is received handler(wrapper -> { @@ -300,22 +307,6 @@ public class PlayerPackets { } }); - protocol.registerClientbound(ClientboundPackets1_8.UPDATE_HEALTH, new PacketRemapper() { - @Override - public void registerMap() { - map(Type.FLOAT); // 0 - Health - handler(wrapper -> { - float health = wrapper.get(Type.FLOAT, 0); - if (health <= 0) { - // Client unloads chunks on respawn, take note - ClientChunks cc = wrapper.user().get(ClientChunks.class); - cc.getBulkChunks().clear(); - cc.getLoadedChunks().clear(); - } - }); - } - }); - protocol.registerClientbound(ClientboundPackets1_8.RESPAWN, new PacketRemapper() { @Override public void registerMap() { @@ -324,11 +315,19 @@ public class PlayerPackets { map(Type.UNSIGNED_BYTE); // 2 - GameMode map(Type.STRING); // 3 - Level Type + // Track player's dimension + handler(new PacketHandler() { + @Override + public void handle(PacketWrapper wrapper) throws Exception { + ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); + int dimensionId = wrapper.get(Type.INT, 0); + clientWorld.setEnvironment(dimensionId); + } + }); + handler(wrapper -> { - // Client unloads chunks on respawn, take note - ClientChunks cc = wrapper.user().get(ClientChunks.class); - cc.getBulkChunks().clear(); - cc.getLoadedChunks().clear(); + // Client unloads chunks on respawn + wrapper.user().get(ClientChunks.class).getLoadedChunks().clear(); int gamemode = wrapper.get(Type.UNSIGNED_BYTE, 0); EntityTracker1_9 tracker = wrapper.user().getEntityTracker(Protocol1_9To1_8.class); diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/packets/WorldPackets.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/packets/WorldPackets.java index afb9cd905..dcb306bab 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/packets/WorldPackets.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/packets/WorldPackets.java @@ -20,8 +20,11 @@ package com.viaversion.viaversion.protocols.protocol1_9to1_8.packets; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.minecraft.BlockFace; import com.viaversion.viaversion.api.minecraft.Position; -import com.viaversion.viaversion.api.minecraft.chunks.Chunk1_8; +import com.viaversion.viaversion.api.minecraft.chunks.BaseChunk; +import com.viaversion.viaversion.api.minecraft.chunks.Chunk; +import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.item.DataItem; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.protocol.Protocol; @@ -29,8 +32,9 @@ import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandler; import com.viaversion.viaversion.api.protocol.remapper.PacketRemapper; import com.viaversion.viaversion.api.type.Type; -import com.viaversion.viaversion.api.type.types.CustomByteType; import com.viaversion.viaversion.protocols.protocol1_8.ClientboundPackets1_8; +import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.storage.ClientWorld; +import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.types.Chunk1_9_1_2Type; import com.viaversion.viaversion.protocols.protocol1_9to1_8.ClientboundPackets1_9; import com.viaversion.viaversion.protocols.protocol1_9to1_8.ItemRewriter; import com.viaversion.viaversion.protocols.protocol1_9to1_8.Protocol1_9To1_8; @@ -40,10 +44,11 @@ import com.viaversion.viaversion.protocols.protocol1_9to1_8.sounds.Effect; import com.viaversion.viaversion.protocols.protocol1_9to1_8.sounds.SoundEffect; import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.ClientChunks; import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.EntityTracker1_9; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.types.Chunk1_9to1_8Type; -import io.netty.buffer.ByteBuf; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.types.Chunk1_8Type; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.types.ChunkBulk1_8Type; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.ArrayList; import java.util.Optional; public class WorldPackets { @@ -132,22 +137,54 @@ public class WorldPackets { handler(new PacketHandler() { @Override public void handle(PacketWrapper wrapper) throws Exception { + ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); ClientChunks clientChunks = wrapper.user().get(ClientChunks.class); - Chunk1_9to1_8Type type = new Chunk1_9to1_8Type(clientChunks); - Chunk1_8 chunk = (Chunk1_8) wrapper.read(type); - if (chunk.isUnloadPacket()) { - wrapper.setPacketType(ClientboundPackets1_9.UNLOAD_CHUNK); + Chunk chunk = wrapper.read(new Chunk1_8Type(clientWorld)); + // Check if the chunk should be handled as an unload packet + if (chunk.isFullChunk() && chunk.getBitmask() == 0) { + wrapper.setPacketType(ClientboundPackets1_9.UNLOAD_CHUNK); wrapper.write(Type.INT, chunk.getX()); wrapper.write(Type.INT, chunk.getZ()); + // Remove commandBlocks on chunk unload CommandBlockProvider provider = Via.getManager().getProviders().get(CommandBlockProvider.class); provider.unloadChunk(wrapper.user(), chunk.getX(), chunk.getZ()); + + clientChunks.getLoadedChunks().remove(ClientChunks.toLong(chunk.getX(), chunk.getZ())); + + // Unload the empty chunks + if (Via.getConfig().isChunkBorderFix()) { + for (BlockFace face : BlockFace.HORIZONTAL) { + int chunkX = chunk.getX() + face.getModX(); + int chunkZ = chunk.getZ() + face.getModZ(); + if (!clientChunks.getLoadedChunks().contains(ClientChunks.toLong(chunkX, chunkZ))) { + PacketWrapper unloadChunk = wrapper.create(ClientboundPackets1_9.UNLOAD_CHUNK); + unloadChunk.write(Type.INT, chunkX); + unloadChunk.write(Type.INT, chunkZ); + unloadChunk.send(Protocol1_9To1_8.class); + } + } + } } else { - wrapper.write(type, chunk); - // eat any other data (Usually happens with unload packets) + wrapper.write(new Chunk1_9_1_2Type(clientWorld), chunk); + + clientChunks.getLoadedChunks().add(ClientChunks.toLong(chunk.getX(), chunk.getZ())); + + // Send empty chunks surrounding the loaded chunk to force 1.9+ clients to render the new chunk + if (Via.getConfig().isChunkBorderFix()) { + for (BlockFace face : BlockFace.HORIZONTAL) { + int chunkX = chunk.getX() + face.getModX(); + int chunkZ = chunk.getZ() + face.getModZ(); + if (!clientChunks.getLoadedChunks().contains(ClientChunks.toLong(chunkX, chunkZ))) { + PacketWrapper emptyChunk = wrapper.create(ClientboundPackets1_9.CHUNK_DATA); + Chunk c = new BaseChunk(chunkX, chunkZ, true, false, 0, new ChunkSection[16], new int[256], new ArrayList<>()); + emptyChunk.write(new Chunk1_9_1_2Type(wrapper.user().get(ClientWorld.class)), c); + emptyChunk.send(Protocol1_9To1_8.class); + } + } + } } - wrapper.read(Type.REMAINING_BYTES); } }); } @@ -158,41 +195,29 @@ public class WorldPackets { public void registerMap() { handler(wrapper -> { wrapper.cancel(); // Cancel the packet from being sent - - boolean skyLight = wrapper.read(Type.BOOLEAN); - int count = wrapper.read(Type.VAR_INT); - - ChunkBulkSection[] chunks = new ChunkBulkSection[count]; - for (int i = 0; i < count; i++) { - chunks[i] = new ChunkBulkSection(wrapper, skyLight); - } - + ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); ClientChunks clientChunks = wrapper.user().get(ClientChunks.class); - for (ChunkBulkSection chunk : chunks) { - // Data is at the end - CustomByteType customByteType = new CustomByteType(chunk.getLength()); - chunk.setData(wrapper.read(customByteType)); + Chunk[] chunks = wrapper.read(new ChunkBulk1_8Type(clientWorld)); - clientChunks.getBulkChunks().add(ClientChunks.toLong(chunk.getX(), chunk.getZ())); // Store for later + // Split into multiple chunk packets + for (Chunk chunk : chunks) { + PacketWrapper chunkData = wrapper.create(ClientboundPackets1_9.CHUNK_DATA); + chunkData.write(new Chunk1_9_1_2Type(clientWorld), chunk); + chunkData.send(Protocol1_9To1_8.class); - // Construct chunk packet - ByteBuf buffer = null; - try { - buffer = wrapper.user().getChannel().alloc().buffer(); + clientChunks.getLoadedChunks().add(ClientChunks.toLong(chunk.getX(), chunk.getZ())); - Type.INT.write(buffer, chunk.getX()); - Type.INT.write(buffer, chunk.getZ()); - Type.BOOLEAN.write(buffer, true); // Always ground-up - Type.UNSIGNED_SHORT.write(buffer, chunk.getBitMask()); - Type.VAR_INT.writePrimitive(buffer, chunk.getLength()); - customByteType.write(buffer, chunk.getData()); - - // Send through this protocol again - PacketWrapper chunkPacket = PacketWrapper.create(ClientboundPackets1_8.CHUNK_DATA, buffer, wrapper.user()); - chunkPacket.send(Protocol1_9To1_8.class, false); - } finally { - if (buffer != null) { - buffer.release(); + // Send empty chunks surrounding the loaded chunk to force 1.9+ clients to render the new chunk + if (Via.getConfig().isChunkBorderFix()) { + for (BlockFace face : BlockFace.HORIZONTAL) { + int chunkX = chunk.getX() + face.getModX(); + int chunkZ = chunk.getZ() + face.getModZ(); + if (!clientChunks.getLoadedChunks().contains(ClientChunks.toLong(chunkX, chunkZ))) { + PacketWrapper emptyChunk = wrapper.create(ClientboundPackets1_9.CHUNK_DATA); + Chunk c = new BaseChunk(chunkX, chunkZ, true, false, 0, new ChunkSection[16], new int[256], new ArrayList<>()); + emptyChunk.write(new Chunk1_9_1_2Type(wrapper.user().get(ClientWorld.class)), c); + emptyChunk.send(Protocol1_9To1_8.class); + } } } } @@ -237,14 +262,6 @@ public class WorldPackets { } }); - protocol.registerClientbound(ClientboundPackets1_8.BLOCK_CHANGE, new PacketRemapper() { - @Override - public void registerMap() { - map(Type.POSITION); - map(Type.VAR_INT); - } - }); - /* Incoming Packets */ protocol.registerServerbound(ServerboundPackets1_9.UPDATE_SIGN, new PacketRemapper() { diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/storage/ClientChunks.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/storage/ClientChunks.java index 881ed3c4f..4bacfc0b6 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/storage/ClientChunks.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/storage/ClientChunks.java @@ -25,21 +25,17 @@ import java.util.Set; public class ClientChunks extends StoredObject { private final Set loadedChunks = Sets.newConcurrentHashSet(); - private final Set bulkChunks = Sets.newConcurrentHashSet(); public ClientChunks(UserConnection connection) { super(connection); } public static long toLong(int msw, int lsw) { - return ((long) msw << 32) + lsw - -2147483648L; + return ((long) msw << 32) + lsw + 2147483648L; } public Set getLoadedChunks() { return loadedChunks; } - public Set getBulkChunks() { - return bulkChunks; - } } diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_8Type.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_8Type.java new file mode 100644 index 000000000..5ffffab59 --- /dev/null +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_8Type.java @@ -0,0 +1,148 @@ +/* + * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion + * Copyright (C) 2016-2021 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.viaversion.protocols.protocol1_9to1_8.types; + +import com.viaversion.viaversion.api.minecraft.Environment; +import com.viaversion.viaversion.api.minecraft.chunks.BaseChunk; +import com.viaversion.viaversion.api.minecraft.chunks.Chunk; +import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; +import com.viaversion.viaversion.api.type.PartialType; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.api.type.types.minecraft.BaseChunkType; +import com.viaversion.viaversion.api.type.types.version.Types1_8; +import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.storage.ClientWorld; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +import java.util.ArrayList; + +public class Chunk1_8Type extends PartialType { + + public Chunk1_8Type(ClientWorld param) { + super(param, Chunk.class); + } + + @Override + public Class getBaseClass() { + return BaseChunkType.class; + } + + @Override + public Chunk read(ByteBuf input, ClientWorld world) throws Exception { + final int chunkX = input.readInt(); + final int chunkZ = input.readInt(); + final boolean fullChunk = input.readBoolean(); + final int bitmask = input.readUnsignedShort(); + final int dataLength = Type.VAR_INT.readPrimitive(input); + final byte[] data = new byte[dataLength]; + input.readBytes(data); + + // Check if the chunk is an unload packet and return early + if (fullChunk && bitmask == 0) { + return new BaseChunk(chunkX, chunkZ, true, false, 0, new ChunkSection[16], null, new ArrayList<>()); + } + + return deserialize(chunkX, chunkZ, fullChunk, world.getEnvironment() == Environment.NORMAL, bitmask, data); + } + + @Override + public void write(ByteBuf output, ClientWorld world, Chunk chunk) throws Exception { + output.writeInt(chunk.getX()); + output.writeInt(chunk.getZ()); + output.writeBoolean(chunk.isFullChunk()); + output.writeShort(chunk.getBitmask()); + final byte[] data = serialize(chunk); + Type.VAR_INT.writePrimitive(output, data.length); + output.writeBytes(data); + } + + // Used for normal and bulk chunks + public static Chunk deserialize(final int chunkX, final int chunkZ, final boolean fullChunk, final boolean skyLight, final int bitmask, final byte[] data) throws Exception { + final ByteBuf input = Unpooled.wrappedBuffer(data); + + final ChunkSection[] sections = new ChunkSection[16]; + int[] biomeData = null; + + // Read blocks + for (int i = 0; i < sections.length; i++) { + if ((bitmask & 1 << i) == 0) continue; + sections[i] = Types1_8.CHUNK_SECTION.read(input); + } + + // Read block light + for (int i = 0; i < sections.length; i++) { + if ((bitmask & 1 << i) == 0) continue; + sections[i].getLight().readBlockLight(input); + } + + // Read sky light + if (skyLight) { + for (int i = 0; i < sections.length; i++) { + if ((bitmask & 1 << i) == 0) continue; + sections[i].getLight().readSkyLight(input); + } + } + + // Read biome data + if (fullChunk) { + biomeData = new int[256]; + for (int i = 0; i < 256; i++) { + biomeData[i] = input.readUnsignedByte(); + } + } + input.release(); + + return new BaseChunk(chunkX, chunkZ, fullChunk, false, bitmask, sections, biomeData, new ArrayList<>()); + } + + // Used for normal and bulk chunks + public static byte[] serialize(final Chunk chunk) throws Exception { + final ByteBuf output = Unpooled.buffer(); + + // Write blocks + for (int i = 0; i < chunk.getSections().length; i++) { + if ((chunk.getBitmask() & 1 << i) == 0) continue; + Types1_8.CHUNK_SECTION.write(output, chunk.getSections()[i]); + } + + // Write block light + for (int i = 0; i < chunk.getSections().length; i++) { + if ((chunk.getBitmask() & 1 << i) == 0) continue; + chunk.getSections()[i].getLight().writeBlockLight(output); + } + + // Write sky light + for (int i = 0; i < chunk.getSections().length; i++) { + if ((chunk.getBitmask() & 1 << i) == 0) continue; + if (chunk.getSections()[i].getLight().hasSkyLight()) chunk.getSections()[i].getLight().writeSkyLight(output); + } + + // Write biome data + if (chunk.isFullChunk() && chunk.getBiomeData() != null) { + for (int biome : chunk.getBiomeData()) { + output.writeByte((byte) biome); + } + } + final byte[] data = new byte[output.readableBytes()]; + output.readBytes(data); + output.release(); + + return data; + } + +} diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_9to1_8Type.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_9to1_8Type.java deleted file mode 100644 index 3245d84ab..000000000 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_9to1_8Type.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion - * Copyright (C) 2016-2021 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.viaversion.protocols.protocol1_9to1_8.types; - -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.viaversion.viaversion.api.Via; -import com.viaversion.viaversion.api.minecraft.chunks.Chunk; -import com.viaversion.viaversion.api.minecraft.chunks.Chunk1_8; -import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; -import com.viaversion.viaversion.api.minecraft.chunks.ChunkSectionLight; -import com.viaversion.viaversion.api.type.PartialType; -import com.viaversion.viaversion.api.type.Type; -import com.viaversion.viaversion.api.type.types.minecraft.BaseChunkType; -import com.viaversion.viaversion.api.type.types.version.Types1_8; -import com.viaversion.viaversion.api.type.types.version.Types1_9; -import com.viaversion.viaversion.protocols.protocol1_10to1_9_3.Protocol1_10To1_9_3_4; -import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.ClientChunks; -import io.netty.buffer.ByteBuf; - -import java.util.ArrayList; -import java.util.BitSet; -import java.util.logging.Level; - -public class Chunk1_9to1_8Type extends PartialType { - /** - * Amount of sections in a chunks. - */ - public static final int SECTION_COUNT = 16; - /** - * size of each chunks section (16x16x16). - */ - private static final int SECTION_SIZE = 16; - /** - * Length of biome data. - */ - private static final int BIOME_DATA_LENGTH = 256; - - public Chunk1_9to1_8Type(ClientChunks chunks) { - super(chunks, Chunk.class); - } - - private static long toLong(int msw, int lsw) { - return ((long) msw << 32) + lsw - -2147483648L; - } - - @Override - public Class getBaseClass() { - return BaseChunkType.class; - } - - @Override - public Chunk read(ByteBuf input, ClientChunks param) throws Exception { - boolean replacePistons = param.getUser().getProtocolInfo().getPipeline().contains(Protocol1_10To1_9_3_4.class) && Via.getConfig().isReplacePistons(); - int replacementId = Via.getConfig().getPistonReplacementId(); - - int chunkX = input.readInt(); - int chunkZ = input.readInt(); - long chunkHash = toLong(chunkX, chunkZ); - boolean fullChunk = input.readByte() != 0; - int bitmask = input.readUnsignedShort(); - int dataLength = Type.VAR_INT.readPrimitive(input); - - // Data to be read - BitSet usedSections = new BitSet(16); - ChunkSection[] sections = new ChunkSection[16]; - int[] biomeData = null; - - // Calculate section count from bitmask - for (int i = 0; i < 16; i++) { - if ((bitmask & (1 << i)) != 0) { - usedSections.set(i); - } - } - int sectionCount = usedSections.cardinality(); // the amount of sections set - - // If the chunks is from a chunks bulk, it is never an unload packet - // Other wise, if it has no data, it is :) - boolean isBulkPacket = param.getBulkChunks().remove(chunkHash); - if (sectionCount == 0 && fullChunk && !isBulkPacket && param.getLoadedChunks().contains(chunkHash)) { - // This is a chunks unload packet - param.getLoadedChunks().remove(chunkHash); - return new Chunk1_8(chunkX, chunkZ); - } - - int startIndex = input.readerIndex(); - param.getLoadedChunks().add(chunkHash); // mark chunks as loaded - - // Read blocks - for (int i = 0; i < SECTION_COUNT; i++) { - if (!usedSections.get(i)) continue; // Section not set - ChunkSection section = Types1_8.CHUNK_SECTION.read(input); - sections[i] = section; - - if (replacePistons) { - section.replacePaletteEntry(36, replacementId); - } - } - - // Read block light - for (int i = 0; i < SECTION_COUNT; i++) { - if (!usedSections.get(i)) continue; // Section not set, has no light - sections[i].getLight().readBlockLight(input); - } - - // Read sky light - int bytesLeft = dataLength - (input.readerIndex() - startIndex); - if (bytesLeft >= ChunkSectionLight.LIGHT_LENGTH) { - for (int i = 0; i < SECTION_COUNT; i++) { - if (!usedSections.get(i)) continue; // Section not set, has no light - sections[i].getLight().readSkyLight(input); - bytesLeft -= ChunkSectionLight.LIGHT_LENGTH; - } - } - - // Read biome data - if (bytesLeft >= BIOME_DATA_LENGTH) { - biomeData = new int[BIOME_DATA_LENGTH]; - for (int i = 0; i < BIOME_DATA_LENGTH; i++) { - biomeData[i] = input.readByte() & 0xFF; - } - bytesLeft -= BIOME_DATA_LENGTH; - } - - // Check remaining bytes - if (bytesLeft > 0) { - Via.getPlatform().getLogger().log(Level.WARNING, bytesLeft + " Bytes left after reading chunks! (" + fullChunk + ")"); - } - - // Return chunks - return new Chunk1_8(chunkX, chunkZ, fullChunk, bitmask, sections, biomeData, new ArrayList()); - } - - @Override - public void write(ByteBuf output, ClientChunks param, Chunk input) throws Exception { - if (!(input instanceof Chunk1_8)) throw new Exception("Incompatible chunk, " + input.getClass()); - - Chunk1_8 chunk = (Chunk1_8) input; - // Write primary info - output.writeInt(chunk.getX()); - output.writeInt(chunk.getZ()); - if (chunk.isUnloadPacket()) return; - output.writeByte(chunk.isFullChunk() ? 0x01 : 0x00); - Type.VAR_INT.writePrimitive(output, chunk.getBitmask()); - - ByteBuf buf = output.alloc().buffer(); - try { - for (int i = 0; i < SECTION_COUNT; i++) { - ChunkSection section = chunk.getSections()[i]; - if (section == null) continue; // Section not set - Types1_9.CHUNK_SECTION.write(buf, section); - section.getLight().writeBlockLight(buf); - - if (!section.getLight().hasSkyLight()) continue; // No sky light, we're done here. - section.getLight().writeSkyLight(buf); - } - buf.readerIndex(0); - Type.VAR_INT.writePrimitive(output, buf.readableBytes() + (chunk.hasBiomeData() ? 256 : 0)); - output.writeBytes(buf); - } finally { - buf.release(); // release buffer - } - - // Write biome data - if (chunk.hasBiomeData()) { - for (int biome : chunk.getBiomeData()) { - output.writeByte((byte) biome); - } - } - } -} diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/ChunkBulk1_8Type.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/ChunkBulk1_8Type.java new file mode 100644 index 000000000..c628fba75 --- /dev/null +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_9to1_8/types/ChunkBulk1_8Type.java @@ -0,0 +1,127 @@ +/* + * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion + * Copyright (C) 2016-2021 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.viaversion.protocols.protocol1_9to1_8.types; + +import com.viaversion.viaversion.api.minecraft.chunks.Chunk; +import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; +import com.viaversion.viaversion.api.type.PartialType; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.api.type.types.minecraft.BaseChunkBulkType; +import com.viaversion.viaversion.protocols.protocol1_9_3to1_9_1_2.storage.ClientWorld; +import io.netty.buffer.ByteBuf; + +public class ChunkBulk1_8Type extends PartialType { + + private static final int BLOCKS_PER_SECTION = 16 * 16 * 16; + private static final int BLOCKS_BYTES = BLOCKS_PER_SECTION * 2; + private static final int LIGHT_BYTES = BLOCKS_PER_SECTION / 2; + private static final int BIOME_BYTES = 16 * 16; + + public ChunkBulk1_8Type(final ClientWorld clientWorld) { + super(clientWorld, Chunk[].class); + } + + @Override + public Class getBaseClass() { + return BaseChunkBulkType.class; + } + + @Override + public Chunk[] read(ByteBuf input, ClientWorld world) throws Exception { + final boolean skyLight = input.readBoolean(); + final int count = Type.VAR_INT.readPrimitive(input); + final Chunk[] chunks = new Chunk[count]; + final ChunkBulkSection[] chunkInfo = new ChunkBulkSection[count]; + + // Read metadata + for (int i = 0; i < chunkInfo.length; i++) { + chunkInfo[i] = new ChunkBulkSection(input, skyLight); + } + // Read data + for (int i = 0; i < chunks.length; i++) { + final ChunkBulkSection chunkBulkSection = chunkInfo[i]; + chunkBulkSection.readData(input); + chunks[i] = Chunk1_8Type.deserialize(chunkBulkSection.chunkX, chunkBulkSection.chunkZ, true, skyLight, chunkBulkSection.bitmask, chunkBulkSection.getData()); + } + + return chunks; + } + + @Override + public void write(ByteBuf output, ClientWorld world, Chunk[] chunks) throws Exception { + boolean skyLight = false; + for (Chunk c : chunks) { + for (ChunkSection section : c.getSections()) { + if (section != null) { + if (section.getLight().hasSkyLight()) { + skyLight = true; + } + } + } + } + output.writeBoolean(skyLight); + Type.VAR_INT.writePrimitive(output, chunks.length); + + // Write metadata + for (Chunk c : chunks) { + output.writeInt(c.getX()); + output.writeInt(c.getZ()); + output.writeShort(c.getBitmask()); + } + // Write data + for (Chunk c : chunks) { + output.writeBytes(Chunk1_8Type.serialize(c)); + } + } + + public static final class ChunkBulkSection { + private final int chunkX; + private final int chunkZ; + private final int bitmask; + private final byte[] data; + + public ChunkBulkSection(final ByteBuf input, final boolean skyLight) { + this.chunkX = input.readInt(); + this.chunkZ = input.readInt(); + this.bitmask = input.readUnsignedShort(); + final int setSections = Integer.bitCount(this.bitmask); + this.data = new byte[setSections * (BLOCKS_BYTES + (skyLight ? 2 * LIGHT_BYTES : LIGHT_BYTES)) + BIOME_BYTES]; + } + + public void readData(final ByteBuf input) { + input.readBytes(this.data); + } + + public int getChunkX() { + return this.chunkX; + } + + public int getChunkZ() { + return this.chunkZ; + } + + public int getBitmask() { + return this.bitmask; + } + + public byte[] getData() { + return this.data; + } + } + +} diff --git a/common/src/main/java/com/viaversion/viaversion/rewriter/EntityRewriter.java b/common/src/main/java/com/viaversion/viaversion/rewriter/EntityRewriter.java index a6803f4cd..b53cbfed4 100644 --- a/common/src/main/java/com/viaversion/viaversion/rewriter/EntityRewriter.java +++ b/common/src/main/java/com/viaversion/viaversion/rewriter/EntityRewriter.java @@ -276,7 +276,7 @@ public abstract class EntityRewriter extends RewriterBase * @param entityType entity type * @param intType int type of the entity id */ - public void registerTracker(ClientboundPacketType packetType, EntityType entityType, Type intType) { + public void registerTracker(ClientboundPacketType packetType, EntityType entityType, Type intType) { protocol.registerClientbound(packetType, new PacketRemapper() { @Override public void registerMap() { diff --git a/common/src/main/resources/assets/viaversion/config.yml b/common/src/main/resources/assets/viaversion/config.yml index 593117d2d..3b179628d 100644 --- a/common/src/main/resources/assets/viaversion/config.yml +++ b/common/src/main/resources/assets/viaversion/config.yml @@ -197,6 +197,8 @@ anti-xray-patch: true replace-pistons: false # What id should we replace with, default is air. (careful of players getting stuck standing on them) replacement-piston-id: 0 +# Fix 1.9+ clients not rendering the far away chunks and improve chunk rendering when moving fast (Increases network usage and decreases client fps slightly) +chunk-border-fix: false # Force the string -> json transform force-json-transform: false # Minimize the cooldown animation in 1.8 servers