From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Cryptite Date: Wed, 13 Nov 2024 08:07:59 -0600 Subject: [PATCH] Equipment Packet Caching diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java index 830bd76916e26a3a54954d3cf7b7520af52a2258..dc9c7a844c2aee1dae80006eafe085c6fa126ae1 100644 --- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java +++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java @@ -30,6 +30,16 @@ public class ClientboundSetEquipmentPacket implements Packet> equipmentList, net.minecraft.world.entity.LivingEntity entity, boolean sanitize, String tag) { + this.entity = id; + this.sanitize = sanitize; + slots = new java.util.ArrayList<>(equipmentList.size()); + for (Pair pair : equipmentList) { + EquipmentSlot slot = pair.getFirst(); + slots.add(Pair.of(slot, entity.getOrCreateCachedEquipmentItem(tag, slot, pair.getSecond()))); + } + } + private ClientboundSetEquipmentPacket(RegistryFriendlyByteBuf buf) { this.entity = buf.readVarInt(); this.slots = Lists.newArrayList(); diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java index 90eb4927fa51ce3df86aa7b6c71f49150a03e337..a6c3beb9d5ab87fca882e1761983ebe385011296 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -388,22 +388,7 @@ public class ServerEntity { Entity entity = this.entity; if (entity instanceof LivingEntity entityliving) { - List> list = Lists.newArrayList(); - Iterator iterator = EquipmentSlot.VALUES.iterator(); - - while (iterator.hasNext()) { - EquipmentSlot enumitemslot = (EquipmentSlot) iterator.next(); - ItemStack itemstack = entityliving.getItemBySlot(enumitemslot); - - if (!itemstack.isEmpty()) { - list.add(Pair.of(enumitemslot, itemstack.copy())); - } - } - - if (!list.isEmpty()) { - sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization - } - ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending + entityliving.sendEquipment(sender, player); } if (!this.entity.getPassengers().isEmpty()) { diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java index f36a075dbee2b96d01899e02460b1d8443e91749..0634626d4a0bda9ac58f1fd16265ca866ee9f5c3 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -298,6 +298,11 @@ public abstract class LivingEntity extends Entity implements Attackable { public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + // Slice start + private static final java.util.Map> equipmentPacketCache = new java.util.HashMap<>(); + protected final java.util.Map cachedEquipmentPacket = new java.util.HashMap<>(); + // Slice end + @Override public float getBukkitYaw() { return this.getYHeadRot(); @@ -3383,6 +3388,7 @@ public abstract class LivingEntity extends Entity implements Attackable { if (map != null) { this.handleHandSwap(map); if (!map.isEmpty()) { + cachedEquipmentPacket.clear(); // Slice - Must invalidate cached equipment map if we have changes this.handleEquipmentChanges(map); } } @@ -3507,7 +3513,25 @@ public abstract class LivingEntity extends Entity implements Attackable { } }); - ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list, true)); // Paper - data sanitization + + // Slice start + net.minecraft.server.level.ChunkMap.TrackedEntity entityTracker = ((ServerLevel) this.level()).getChunkSource().chunkMap.entityMap.get(getId()); + if (entityTracker != null) { + ClientboundSetEquipmentPacket packet = new ClientboundSetEquipmentPacket(this.getId(), list); + for (net.minecraft.server.network.ServerPlayerConnection playerConnection : entityTracker.seenBy) { + ServerPlayer player = playerConnection.getPlayer(); + org.bukkit.event.player.PlayerReceiveEquipmentEvent event = new org.bukkit.event.player.PlayerReceiveEquipmentEvent(player.getBukkitEntity(), getBukkitEntity()); + level().getCraftServer().getPluginManager().callEvent(event); + + String tag = event.getTag(); + if (tag != null) { + playerConnection.send(new ClientboundSetEquipmentPacket(this.getId(), list, this, false, tag)); + } else { + playerConnection.send(packet); + } + } + } + // Slice end } private ItemStack getLastArmorItem(EquipmentSlot slot) { @@ -4803,4 +4827,84 @@ public abstract class LivingEntity extends Entity implements Attackable { public static record Fallsounds(SoundEvent small, SoundEvent big) { } + + // Slice start + public static void invalidateCachedEquipment(String tag) { + com.google.common.cache.Cache removedCache = equipmentPacketCache.remove(tag); + if (removedCache != null) { + removedCache.asMap().clear(); + } + } + + public ItemStack getOrCreateCachedEquipmentItem(String tag, EquipmentSlot slot, ItemStack itemStack) { + return equipmentPacketCache.computeIfAbsent(tag, s -> com.google.common.cache.CacheBuilder.newBuilder() + .expireAfterAccess(10, java.util.concurrent.TimeUnit.MINUTES) + .build()) + .asMap() + .computeIfAbsent(itemStack.hashCode(), i -> { + String name = slot.name(); + + //How neat is this. + if (name.equals("MAINHAND")) { + name = "HAND"; + } else if (name.equals("OFFHAND")) { + name = "OFF_HAND"; + } + + org.bukkit.event.entity.EntityEquipmentItemLookup event = new org.bukkit.event.entity.EntityEquipmentItemLookup(getBukkitEntity(), tag, org.bukkit.inventory.EquipmentSlot.valueOf(name), CraftItemStack.asBukkitCopy(itemStack)); + this.level().getCraftServer().getPluginManager().callEvent(event); + org.bukkit.inventory.ItemStack eventItem = event.getItemStack(); + return CraftItemStack.asNMSCopy(eventItem); + }); + } + // Slice end + + // Slice start + public void sendEquipment(java.util.function.Consumer> consumer, ServerPlayer p) { + org.bukkit.event.player.PlayerReceiveEquipmentEvent event = new org.bukkit.event.player.PlayerReceiveEquipmentEvent(p.getBukkitEntity(), getBukkitEntity()); + level().getCraftServer().getPluginManager().callEvent(event); + + boolean sendEquipment = !event.isCancelled(); + String tag = event.getTag(); + if (sendEquipment && this instanceof ServerPlayer player && tag != null) { + ClientboundSetEquipmentPacket equipmentPacket = player.cachedEquipmentPacket.get(tag); + if (equipmentPacket != null) { + //Event says use a tag, and our tag exists; so we simply used our entire cached packet + consumer.accept(equipmentPacket); + return; + } + } + + if (sendEquipment) { + List> list = Lists.newArrayList(); + Iterator iterator = EquipmentSlot.VALUES.iterator(); + + while (iterator.hasNext()) { + EquipmentSlot enumitemslot = (EquipmentSlot) iterator.next(); + ItemStack itemstack = getItemBySlot(enumitemslot); + + if (!itemstack.isEmpty()) { + ItemStack finalItemStack; + if (tag != null) { + finalItemStack = getOrCreateCachedEquipmentItem(tag, enumitemslot, itemstack); + } else { + finalItemStack = itemstack.copy(); + } + + list.add(Pair.of(enumitemslot, finalItemStack)); + } + } + + if (!list.isEmpty()) { + ClientboundSetEquipmentPacket equipmentPacket = new ClientboundSetEquipmentPacket(getId(), list, true); + if (tag != null) { + cachedEquipmentPacket.put(tag, equipmentPacket); + } + consumer.accept(equipmentPacket); + } + + detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending + } + } + // Slice end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index d0c409f4efad289e3e325f44b500fc72589d89d4..baecf1c7bb459786bdbe6f7381ef148e0dad0ef5 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -1205,6 +1205,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { } // Paper end - body yaw API + // Slice start + @Override + public void sendEquipment(Player p) { + if (entity instanceof net.minecraft.world.entity.LivingEntity livingEntity) { + net.minecraft.server.level.ServerPlayer serverPlayer = ((CraftPlayer) p).getHandle(); + livingEntity.sendEquipment(packet -> serverPlayer.connection.send(packet), serverPlayer); + } + } + // Slice end + // Paper start - Expose canUseSlot @Override public boolean canUseEquipmentSlot(org.bukkit.inventory.EquipmentSlot slot) {