From f93c64d6aa6e43ea1acc8ac05e52dd0d8a0977d9 Mon Sep 17 00:00:00 2001 From: Pablo Herrera Date: Sun, 24 Nov 2024 11:19:05 +0100 Subject: [PATCH] Improve 1.21 client enchantments on legacy servers (#4255) Signed-off-by: Pablo Herrera --- .../LegacyChangeItemListener.java | 72 +++++++++++++++++ .../PlayerChangeItemListener.java | 56 ++++++------- .../bukkit/platform/BukkitViaLoader.java | 3 +- .../viaversion/viaversion/ViaListener.java | 5 +- .../rewriter/BlockItemPacketRewriter1_21.java | 34 +++++++- .../rewriter/EntityPacketRewriter1_21.java | 3 +- .../storage/EfficiencyAttributeStorage.java | 79 ++++++++++++++----- 7 files changed, 196 insertions(+), 56 deletions(-) create mode 100644 bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/LegacyChangeItemListener.java diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/LegacyChangeItemListener.java b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/LegacyChangeItemListener.java new file mode 100644 index 000000000..9bfecd4a1 --- /dev/null +++ b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/LegacyChangeItemListener.java @@ -0,0 +1,72 @@ +/* + * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion + * Copyright (C) 2016-2024 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.bukkit.listeners.v1_20_5to1_21; + +import com.viaversion.viaversion.ViaVersionPlugin; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.protocols.v1_20_5to1_21.storage.EfficiencyAttributeStorage; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +public class LegacyChangeItemListener extends PlayerChangeItemListener { + + public LegacyChangeItemListener(final ViaVersionPlugin plugin) { + super(plugin); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBlockDamageEvent(final BlockDamageEvent event) { + final Player player = event.getPlayer(); + final ItemStack item = event.getItemInHand(); + sendAttributeUpdate(player, item, Slot.HAND); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onInventoryClose(final InventoryCloseEvent event) { + if (event.getPlayer() instanceof Player player && + (event.getInventory().getType() == InventoryType.CRAFTING || + event.getInventory().getType() == InventoryType.PLAYER)) { + sendArmorUpdate(player); + } + } + + private void sendArmorUpdate(final Player player) { + final UserConnection connection = getUserConnection(player); + final EfficiencyAttributeStorage storage = getEfficiencyStorage(connection); + if (storage == null) { + return; + } + + final PlayerInventory inventory = player.getInventory(); + final ItemStack helmet = inventory.getHelmet(); + final ItemStack leggings = swiftSneak != null ? inventory.getLeggings() : null; + final ItemStack boots = depthStrider != null ? inventory.getBoots() : null; + + storage.setEnchants(player.getEntityId(), connection, storage.activeEnchants() + .aquaAffinity(helmet != null ? helmet.getEnchantmentLevel(aquaAffinity) : 0) + .swiftSneak(leggings != null ? leggings.getEnchantmentLevel(swiftSneak) : 0) + .depthStrider(boots != null ? boots.getEnchantmentLevel(depthStrider) : 0)); + } + +} diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/PlayerChangeItemListener.java b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/PlayerChangeItemListener.java index 3af953882..1a56c5a5d 100644 --- a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/PlayerChangeItemListener.java +++ b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/PlayerChangeItemListener.java @@ -18,7 +18,6 @@ package com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21; import com.viaversion.viaversion.ViaVersionPlugin; -import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.bukkit.listeners.ViaBukkitListener; import com.viaversion.viaversion.protocols.v1_20_5to1_21.Protocol1_20_5To1_21; @@ -38,11 +37,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; public class PlayerChangeItemListener extends ViaBukkitListener { // Use legacy function and names here to support all versions - private final Enchantment efficiency = getByName("efficiency", "DIG_SPEED"); - private final Enchantment aquaAffinity = getByName("aqua_affinity", "WATER_WORKER"); - private final Enchantment depthStrider = getByName("depth_strider", "DEPTH_STRIDER"); - private final Enchantment soulSpeed = getByName("soul_speed", "SOUL_SPEED"); - private final Enchantment swiftSneak = getByName("swift_sneak", "SWIFT_SNEAK"); + protected final Enchantment efficiency = getByName("efficiency", "DIG_SPEED"); + protected final Enchantment aquaAffinity = getByName("aqua_affinity", "WATER_WORKER"); + protected final Enchantment depthStrider = getByName("depth_strider", "DEPTH_STRIDER"); + protected final Enchantment soulSpeed = getByName("soul_speed", "SOUL_SPEED"); + protected final Enchantment swiftSneak = getByName("swift_sneak", "SWIFT_SNEAK"); public PlayerChangeItemListener(final ViaVersionPlugin plugin) { super(plugin, Protocol1_20_5To1_21.class); @@ -55,35 +54,26 @@ public class PlayerChangeItemListener extends ViaBukkitListener { sendAttributeUpdate(player, item, Slot.HAND); } + protected EfficiencyAttributeStorage getEfficiencyStorage(final UserConnection connection) { + return connection != null ? connection.get(EfficiencyAttributeStorage.class) : null; + } + void sendAttributeUpdate(final Player player, @Nullable final ItemStack item, final Slot slot) { - final UserConnection connection = Via.getAPI().getConnection(player.getUniqueId()); - if (connection == null || !isOnPipe(player)) { - return; - } + final UserConnection connection = getUserConnection(player); + final EfficiencyAttributeStorage storage = getEfficiencyStorage(connection); + if (storage == null) return; - final EfficiencyAttributeStorage storage = connection.get(EfficiencyAttributeStorage.class); - if (storage == null) { - return; - } - - final EfficiencyAttributeStorage.ActiveEnchants activeEnchants = storage.activeEnchants(); - int efficiencyLevel = activeEnchants.efficiency().level(); - int aquaAffinityLevel = activeEnchants.aquaAffinity().level(); - int soulSpeedLevel = activeEnchants.soulSpeed().level(); - int swiftSneakLevel = activeEnchants.swiftSneak().level(); - int depthStriderLevel = activeEnchants.depthStrider().level(); - switch (slot) { - case HAND -> efficiencyLevel = item != null ? item.getEnchantmentLevel(efficiency) : 0; - case HELMET -> aquaAffinityLevel = item != null ? item.getEnchantmentLevel(aquaAffinity) : 0; - case LEGGINGS -> swiftSneakLevel = item != null && swiftSneak != null ? item.getEnchantmentLevel(swiftSneak) : 0; - case BOOTS -> { - depthStriderLevel = item != null && depthStrider != null ? item.getEnchantmentLevel(depthStrider) : 0; - // TODO This needs continuous ticking for the supporting block as a conditional effect - // and is even more prone to desync from high ping than the other attributes - //soulSpeedLevel = item != null && soulSpeed != null ? item.getEnchantmentLevel(soulSpeed) : 0; - } - } - storage.setEnchants(player.getEntityId(), connection, efficiencyLevel, soulSpeedLevel, swiftSneakLevel, aquaAffinityLevel, depthStriderLevel); + EfficiencyAttributeStorage.ActiveEnchants enchants = storage.activeEnchants(); + enchants = switch (slot) { + case HAND -> enchants.efficiency(item != null ? item.getEnchantmentLevel(efficiency) : 0); + case HELMET -> enchants.aquaAffinity(item != null ? item.getEnchantmentLevel(aquaAffinity) : 0); + case LEGGINGS -> enchants.swiftSneak(item != null && swiftSneak != null ? item.getEnchantmentLevel(swiftSneak) : 0); + case BOOTS -> enchants.depthStrider(item != null && depthStrider != null ? item.getEnchantmentLevel(depthStrider) : 0); + // TODO This needs continuous ticking for the supporting block as a conditional effect + // and is even more prone to desync from high ping than the other attributes + //soulSpeedLevel = item != null && soulSpeed != null ? item.getEnchantmentLevel(soulSpeed) : 0; + }; + storage.setEnchants(player.getEntityId(), connection, enchants); } enum Slot { diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java index 12cfc29fb..83d33f6e0 100644 --- a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java +++ b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java @@ -28,6 +28,7 @@ import com.viaversion.viaversion.bukkit.listeners.multiversion.PlayerSneakListen import com.viaversion.viaversion.bukkit.listeners.v1_14_4to1_15.EntityToggleGlideListener; import com.viaversion.viaversion.bukkit.listeners.v1_19_3to1_19_4.ArmorToggleListener; import com.viaversion.viaversion.bukkit.listeners.v1_18_2to1_19.BlockBreakListener; +import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.LegacyChangeItemListener; import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.ArmorListener; import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.BlockListener; import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.DeathListener; @@ -184,7 +185,7 @@ public class BukkitViaLoader implements ViaPlatformLoader { if (PaperViaInjector.hasClass("io.papermc.paper.event.player.PlayerInventorySlotChangeEvent")) { new PaperPlayerChangeItemListener(plugin).register(); } else { - new PlayerChangeItemListener(plugin).register(); + new LegacyChangeItemListener(plugin).register(); } } } diff --git a/common/src/main/java/com/viaversion/viaversion/ViaListener.java b/common/src/main/java/com/viaversion/viaversion/ViaListener.java index 7c97b7bd3..4d881af5f 100644 --- a/common/src/main/java/com/viaversion/viaversion/ViaListener.java +++ b/common/src/main/java/com/viaversion/viaversion/ViaListener.java @@ -48,7 +48,10 @@ public abstract class ViaListener { * @return True if on pipe */ protected boolean isOnPipe(UUID uuid) { - UserConnection userConnection = getUserConnection(uuid); + return isOnPipe(getUserConnection(uuid)); + } + + protected boolean isOnPipe(UserConnection userConnection) { return userConnection != null && (requiredPipeline == null || userConnection.getProtocolInfo().getPipeline().contains(requiredPipeline)); } diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/BlockItemPacketRewriter1_21.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/BlockItemPacketRewriter1_21.java index ed21ef5ec..32e6f0a94 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/BlockItemPacketRewriter1_21.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/BlockItemPacketRewriter1_21.java @@ -26,6 +26,7 @@ import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifiers1_20_5; import com.viaversion.viaversion.api.minecraft.item.data.AttributeModifiers1_21; +import com.viaversion.viaversion.api.minecraft.item.data.Enchantments; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.chunk.ChunkType1_20_2; @@ -38,6 +39,7 @@ import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPac import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_5to1_21.Protocol1_20_5To1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.data.AttributeModifierMappings1_21; +import com.viaversion.viaversion.protocols.v1_20_5to1_21.storage.EfficiencyAttributeStorage; import com.viaversion.viaversion.protocols.v1_20_5to1_21.storage.OnGroundTracker; import com.viaversion.viaversion.rewriter.BlockRewriter; import com.viaversion.viaversion.rewriter.StructuredItemRewriter; @@ -48,6 +50,14 @@ import java.util.Objects; public final class BlockItemPacketRewriter1_21 extends StructuredItemRewriter { private static final List DISCS = List.of("11", "13", "5", "blocks", "cat", "chirp", "far", "mall", "mellohi", "otherside", "pigstep", "relic", "stal", "strad", "wait", "ward"); + private static final int HELMET_SLOT = 5; + private static final int CHESTPLATE_SLOT = 6; + private static final int LEGGINGS_SLOT = 7; + private static final int BOOTS_SLOT = 8; + + private static final int AQUA_AFFINITY_ID = 6; + private static final int DEPTH_STRIDER_ID = 8; + private static final int SWIFT_SNEAK_ID = 12; public BlockItemPacketRewriter1_21(final Protocol1_20_5To1_21 protocol) { super(protocol, @@ -67,7 +77,29 @@ public final class BlockItemPacketRewriter1_21 extends StructuredItemRewriter { + final short containerId = wrapper.passthrough(Types.UNSIGNED_BYTE); // Container id + wrapper.passthrough(Types.VAR_INT); // State id + final short slotId = wrapper.passthrough(Types.SHORT); // Slot id + final Item item = handleItemToClient(wrapper.user(), wrapper.read(itemType())); + wrapper.write(mappedItemType(), item); + + // When a players' armor is set, update their attributes + if (containerId != 0 || slotId > BOOTS_SLOT || slotId < HELMET_SLOT || slotId == CHESTPLATE_SLOT) { + return; + } + + final EfficiencyAttributeStorage storage = wrapper.user().get(EfficiencyAttributeStorage.class); + Enchantments enchants = item.dataContainer().get(StructuredDataKey.ENCHANTMENTS); + EfficiencyAttributeStorage.ActiveEnchants active = storage.activeEnchants(); + active = switch (slotId) { + case HELMET_SLOT -> active.aquaAffinity(enchants == null ? 0 : enchants.getLevel(AQUA_AFFINITY_ID)); + case LEGGINGS_SLOT -> active.swiftSneak(enchants == null ? 0 : enchants.getLevel(SWIFT_SNEAK_ID)); + case BOOTS_SLOT -> active.depthStrider(enchants == null ? 0 : enchants.getLevel(DEPTH_STRIDER_ID)); + default -> active; + }; + storage.setEnchants(-1, wrapper.user(), active); + }); registerAdvancements1_20_3(ClientboundPackets1_20_5.UPDATE_ADVANCEMENTS); registerSetEquipment(ClientboundPackets1_20_5.SET_EQUIPMENT); registerContainerClick1_17_1(ServerboundPackets1_20_5.CONTAINER_CLICK); diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/EntityPacketRewriter1_21.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/EntityPacketRewriter1_21.java index 26df9035d..54a45686c 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/EntityPacketRewriter1_21.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/rewriter/EntityPacketRewriter1_21.java @@ -111,7 +111,8 @@ public final class EntityPacketRewriter1_21 extends EntityRewriter wrapper.user().get(EfficiencyAttributeStorage.class).onLoginSent(wrapper.user())); + handler(wrapper -> wrapper.user().get(EfficiencyAttributeStorage.class) + .onLoginSent(wrapper.get(Types.INT, 0), wrapper.user())); } }); diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/storage/EfficiencyAttributeStorage.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/storage/EfficiencyAttributeStorage.java index 485e25399..60f059898 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/storage/EfficiencyAttributeStorage.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/storage/EfficiencyAttributeStorage.java @@ -45,25 +45,10 @@ public final class EfficiencyAttributeStorage implements StorableObject { private volatile boolean loginSent; private ActiveEnchants activeEnchants = DEFAULT; - public void setEnchants(final int entityId, final UserConnection connection, final int efficiency, final int soulSpeed, - final int swiftSneak, final int aquaAffinity, final int depthStrider) { - // Always called from the main thread - if (efficiency == activeEnchants.efficiency.level - && soulSpeed == activeEnchants.soulSpeed.level - && swiftSneak == activeEnchants.swiftSneak.level - && aquaAffinity == activeEnchants.aquaAffinity.level - && depthStrider == activeEnchants.depthStrider.level) { - return; - } - + public void setEnchants(final int entityId, final UserConnection connection, final ActiveEnchants enchants) { + if (activeEnchants == enchants) return; synchronized (lock) { - this.activeEnchants = new ActiveEnchants(entityId, - new ActiveEnchant(activeEnchants.efficiency, efficiency), - new ActiveEnchant(activeEnchants.soulSpeed, soulSpeed), - new ActiveEnchant(activeEnchants.swiftSneak, swiftSneak), - new ActiveEnchant(activeEnchants.aquaAffinity, aquaAffinity), - new ActiveEnchant(activeEnchants.depthStrider, depthStrider) - ); + this.activeEnchants = entityId == -1 ? enchants : enchants.withEntityId(entityId); this.attributesSent = false; } sendAttributesPacket(connection, false); @@ -73,7 +58,10 @@ public final class EfficiencyAttributeStorage implements StorableObject { return activeEnchants; } - public void onLoginSent(final UserConnection connection) { + public void onLoginSent(final int entityId, final UserConnection connection) { + synchronized (lock) { + activeEnchants = activeEnchants.withEntityId(entityId); + } // Always called from the netty thread this.loginSent = true; sendAttributesPacket(connection, false); @@ -128,6 +116,59 @@ public final class EfficiencyAttributeStorage implements StorableObject { public record ActiveEnchants(int entityId, ActiveEnchant efficiency, ActiveEnchant soulSpeed, ActiveEnchant swiftSneak, ActiveEnchant aquaAffinity, ActiveEnchant depthStrider) { + private ActiveEnchants withEntityId(int entityId) { + return this.entityId == entityId ? this : new ActiveEnchants(entityId, + efficiency, + soulSpeed, + swiftSneak, + aquaAffinity, + depthStrider); + } + + public ActiveEnchants efficiency(int level) { + return efficiency.level == level ? this : new ActiveEnchants(entityId, + new ActiveEnchant(efficiency, level), + soulSpeed, + swiftSneak, + aquaAffinity, + depthStrider); + } + + public ActiveEnchants soulSpeed(int level) { + return soulSpeed.level == level ? this : new ActiveEnchants(entityId, + efficiency, + new ActiveEnchant(soulSpeed, level), + swiftSneak, + aquaAffinity, + depthStrider); + } + + public ActiveEnchants swiftSneak(int level) { + return swiftSneak.level == level ? this : new ActiveEnchants(entityId, + efficiency, + soulSpeed, + new ActiveEnchant(swiftSneak, level), + aquaAffinity, + depthStrider); + } + + public ActiveEnchants aquaAffinity(int level) { + return aquaAffinity.level == level ? this : new ActiveEnchants(entityId, + efficiency, + soulSpeed, + swiftSneak, + new ActiveEnchant(aquaAffinity, level), + depthStrider); + } + + public ActiveEnchants depthStrider(int level) { + return depthStrider.level == level ? this : new ActiveEnchants(entityId, + efficiency, + soulSpeed, + swiftSneak, + aquaAffinity, + new ActiveEnchant(depthStrider, level)); + } } public record ActiveEnchant(EnchantAttributeModifier modifier, int previousLevel, int level) {