From b42b04fecda472d868a0e78929e89ff6261dc674 Mon Sep 17 00:00:00 2001 From: CraftBukkit/Spigot Date: Sat, 24 Sep 2022 11:05:14 +1000 Subject: [PATCH] SPIGOT-2620: Add Player#sendBlockChanges() By: Parker Hawke --- .../game/PacketPlayOutMultiBlockChange.patch | 15 ++++++- .../craftbukkit/entity/CraftPlayer.java | 45 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/paper-server/nms-patches/net/minecraft/network/protocol/game/PacketPlayOutMultiBlockChange.patch b/paper-server/nms-patches/net/minecraft/network/protocol/game/PacketPlayOutMultiBlockChange.patch index c0e4b3f5b7..f62e9348f5 100644 --- a/paper-server/nms-patches/net/minecraft/network/protocol/game/PacketPlayOutMultiBlockChange.patch +++ b/paper-server/nms-patches/net/minecraft/network/protocol/game/PacketPlayOutMultiBlockChange.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/network/protocol/game/PacketPlayOutMultiBlockChange.java +++ b/net/minecraft/network/protocol/game/PacketPlayOutMultiBlockChange.java -@@ -32,7 +32,7 @@ +@@ -32,11 +32,20 @@ short short0 = (Short) shortiterator.next(); this.positions[j] = short0; @@ -9,3 +9,16 @@ } } + ++ // CraftBukkit start - Add constructor ++ public PacketPlayOutMultiBlockChange(SectionPosition sectionposition, ShortSet shortset, IBlockData[] states, boolean flag) { ++ this.sectionPos = sectionposition; ++ this.suppressLightUpdates = flag; ++ this.positions = shortset.toShortArray(); ++ this.states = states; ++ } ++ // CraftBukkit end ++ + public PacketPlayOutMultiBlockChange(PacketDataSerializer packetdataserializer) { + this.sectionPos = SectionPosition.of(packetdataserializer.readLong()); + this.suppressLightUpdates = packetdataserializer.readBoolean(); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index a7cda388de..d7e408cd2e 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -6,6 +6,8 @@ import com.google.common.io.BaseEncoding; import com.mojang.authlib.GameProfile; import com.mojang.datafixers.util.Pair; import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.shorts.ShortArraySet; +import it.unimi.dsi.fastutil.shorts.ShortSet; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.ref.WeakReference; @@ -28,6 +30,7 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import net.minecraft.advancements.AdvancementProgress; import net.minecraft.core.BlockPosition; +import net.minecraft.core.SectionPosition; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.PacketDataSerializer; import net.minecraft.network.chat.ChatMessageContent; @@ -51,6 +54,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutEntitySound; import net.minecraft.network.protocol.game.PacketPlayOutExperience; import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange; import net.minecraft.network.protocol.game.PacketPlayOutMap; +import net.minecraft.network.protocol.game.PacketPlayOutMultiBlockChange; import net.minecraft.network.protocol.game.PacketPlayOutNamedSoundEffect; import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo; import net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter; @@ -79,6 +83,7 @@ import net.minecraft.world.item.EnumColor; import net.minecraft.world.level.EnumGamemode; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.entity.TileEntitySign; +import net.minecraft.world.level.block.state.IBlockData; import net.minecraft.world.level.border.IWorldBorderListener; import net.minecraft.world.level.saveddata.maps.MapIcon; import net.minecraft.world.level.saveddata.maps.WorldMap; @@ -101,6 +106,7 @@ import org.bukkit.Statistic; import org.bukkit.WeatherType; import org.bukkit.WorldBorder; import org.bukkit.block.Block; +import org.bukkit.block.BlockState; import org.bukkit.block.Sign; import org.bukkit.block.data.BlockData; import org.bukkit.configuration.serialization.DelegateDeserialization; @@ -118,6 +124,7 @@ import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.CraftWorldBorder; import org.bukkit.craftbukkit.advancement.CraftAdvancement; import org.bukkit.craftbukkit.advancement.CraftAdvancementProgress; +import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.craftbukkit.block.CraftSign; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.craftbukkit.conversations.ConversationTracker; @@ -613,6 +620,44 @@ public class CraftPlayer extends CraftHumanEntity implements Player { getHandle().connection.send(packet); } + @Override + public void sendBlockChanges(Collection blocks, boolean suppressLightUpdates) { + Preconditions.checkArgument(blocks != null, "blocks must not be null"); + + if (getHandle().connection == null || blocks.isEmpty()) { + return; + } + + Map changes = new HashMap<>(); + for (BlockState state : blocks) { + CraftBlockState cstate = (CraftBlockState) state; + BlockPosition blockPosition = cstate.getPosition(); + + // The coordinates of the chunk section in which the block is located, aka chunk x, y, and z + SectionPosition sectionPosition = SectionPosition.of(blockPosition); + + // Push the block change position and block data to the final change map + ChunkSectionChanges sectionChanges = changes.computeIfAbsent(sectionPosition, (ignore) -> new ChunkSectionChanges()); + + sectionChanges.positions().add(SectionPosition.sectionRelativePos(blockPosition)); + sectionChanges.blockData().add(cstate.getHandle()); + } + + // Construct the packets using the data allocated above and send then to the players + for (Map.Entry entry : changes.entrySet()) { + ChunkSectionChanges chunkChanges = entry.getValue(); + PacketPlayOutMultiBlockChange packet = new PacketPlayOutMultiBlockChange(entry.getKey(), chunkChanges.positions(), chunkChanges.blockData().toArray(IBlockData[]::new), suppressLightUpdates); + getHandle().connection.send(packet); + } + } + + private record ChunkSectionChanges(ShortSet positions, List blockData) { + + public ChunkSectionChanges() { + this(new ShortArraySet(), new ArrayList<>()); + } + } + @Override public void sendBlockDamage(Location loc, float progress) { Preconditions.checkArgument(loc != null, "loc must not be null");