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 03576280e..a4a84950f 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 @@ -31,6 +31,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerItemHeldEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -40,6 +41,9 @@ import org.checkerframework.checker.nullness.qual.Nullable; public final class PlayerChangeItemListener extends ViaBukkitListener { private final Enchantment efficiency = Enchantment.getByKey(NamespacedKey.minecraft("efficiency")); + private final Enchantment depthStrider = Enchantment.getByKey(NamespacedKey.minecraft("depth_strider")); + private final Enchantment soulSpeed = Enchantment.getByKey(NamespacedKey.minecraft("soul_speed")); + private final Enchantment swiftSneak = Enchantment.getByKey(NamespacedKey.minecraft("swift_sneak")); public PlayerChangeItemListener(final ViaVersionPlugin plugin) { super(plugin, Protocol1_20_5To1_21.class); @@ -49,8 +53,14 @@ public final class PlayerChangeItemListener extends ViaBukkitListener { public void onPlayerInventorySlotChangedEvent(final PlayerInventorySlotChangeEvent event) { final Player player = event.getPlayer(); final ItemStack item = event.getNewItemStack(); - if (event.getSlot() == player.getInventory().getHeldItemSlot()) { - sendAttributeUpdate(player, item); + final PlayerInventory inventory = player.getInventory(); + final int slot = event.getSlot(); + if (slot == inventory.getHeldItemSlot()) { + sendAttributeUpdate(player, item, Slot.HAND); + } else if (slot == 36) { + sendAttributeUpdate(player, item, Slot.BOOTS); + } else if (slot == 37) { + sendAttributeUpdate(player, item, Slot.LEGGINGS); } } @@ -58,10 +68,10 @@ public final class PlayerChangeItemListener extends ViaBukkitListener { public void onPlayerItemHeld(final PlayerItemHeldEvent event) { final Player player = event.getPlayer(); final ItemStack item = player.getInventory().getItem(event.getNewSlot()); - sendAttributeUpdate(player, item); + sendAttributeUpdate(player, item, Slot.HAND); } - private void sendAttributeUpdate(final Player player, @Nullable final ItemStack item) { + private 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; @@ -72,7 +82,23 @@ public final class PlayerChangeItemListener extends ViaBukkitListener { return; } - final int efficiencyLevel = item != null ? item.getEnchantmentLevel(efficiency) : 0; - storage.setEfficiencyLevel(new EfficiencyAttributeStorage.StoredEfficiency(player.getEntityId(), efficiencyLevel), connection); + final EfficiencyAttributeStorage.ActiveEnchants activeEnchants = storage.activeEnchants(); + int efficiencyLevel = activeEnchants.efficiency().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 LEGGINGS -> swiftSneakLevel = item != null && swiftSneak != null ? item.getEnchantmentLevel(swiftSneak) : 0; + case BOOTS -> { + depthStriderLevel = item != null && depthStrider != null ? item.getEnchantmentLevel(depthStrider) : 0; + soulSpeedLevel = item != null && soulSpeed != null ? item.getEnchantmentLevel(soulSpeed) : 0; + } + } + storage.setEnchants(player.getEntityId(), connection, efficiencyLevel, soulSpeedLevel, swiftSneakLevel, depthStriderLevel); + } + + private enum Slot { + HAND, BOOTS, LEGGINGS } } 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 8b9c5cd16..6c1560fcb 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 @@ -23,57 +23,115 @@ import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.protocols.v1_20_5to1_21.Protocol1_20_5To1_21; import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; +import java.util.List; public final class EfficiencyAttributeStorage implements StorableObject { - private static final int MINING_EFFICIENCY_ID = 19; - private final Object lock = new Object(); // Slightly sloppy locking, but should be good enough + public static final EnchantAttributeModifier EFFICIENCY = new EnchantAttributeModifier("minecraft:enchantment.efficiency/mainhand", 19, 0, level -> (level * level) + 1); + public static final EnchantAttributeModifier SOUL_SPEED = new EnchantAttributeModifier("minecraft:enchantment.soul_speed", 21, 0.1, level -> 0.04D + ((level - 1) * 0.01D)); + public static final EnchantAttributeModifier SWIFT_SNEAK = new EnchantAttributeModifier("minecraft:enchantment.swift_sneak", 25, 0.3, level -> level * 0.15D); + public static final EnchantAttributeModifier DEPTH_STRIDER = new EnchantAttributeModifier("minecraft:enchantment.depth_strider", 30, 0, level -> level / 3D); + private static final ActiveEnchants DEFAULT = new ActiveEnchants(-1, + new ActiveEnchant(EFFICIENCY, 0), + new ActiveEnchant(SOUL_SPEED, 0), + new ActiveEnchant(SWIFT_SNEAK, 0), + new ActiveEnchant(DEPTH_STRIDER, 0) + ); + private final Object lock = new Object(); + private volatile ActiveEnchants activeEnchants = DEFAULT; + private volatile boolean attributesSent = true; private volatile boolean loginSent; - private volatile StoredEfficiency efficiencyLevel; - public void setEfficiencyLevel(final StoredEfficiency efficiencyLevel, final UserConnection connection) { - this.efficiencyLevel = efficiencyLevel; + public void setEnchants(final int entityId, final UserConnection connection, final int efficiency, final int soulSpeed, final int swiftSneak, final int depthStrider) { + // Always called from the main thread + if (efficiency == activeEnchants.efficiency.level + && soulSpeed == activeEnchants.soulSpeed.level + && swiftSneak == activeEnchants.swiftSneak.level + && depthStrider == activeEnchants.depthStrider.level) { + return; + } + + synchronized (lock) { + this.activeEnchants = new ActiveEnchants(entityId, + new ActiveEnchant(EFFICIENCY, efficiency), + new ActiveEnchant(SOUL_SPEED, soulSpeed), + new ActiveEnchant(SWIFT_SNEAK, swiftSneak), + new ActiveEnchant(DEPTH_STRIDER, depthStrider) + ); + this.attributesSent = false; + } sendAttributesPacket(connection); } + public ActiveEnchants activeEnchants() { + return activeEnchants; + } + public void onLoginSent(final UserConnection connection) { + // Always called from the netty thread this.loginSent = true; sendAttributesPacket(connection); } private void sendAttributesPacket(final UserConnection connection) { - final StoredEfficiency efficiency; + final ActiveEnchants enchants; synchronized (lock) { // Older servers (and often Bungee) will send world state packets before sending the login packet - if (!loginSent || efficiencyLevel == null) { + if (!loginSent || attributesSent) { return; } - efficiency = efficiencyLevel; - efficiencyLevel = null; + enchants = this.activeEnchants; + attributesSent = true; } final PacketWrapper attributesPacket = PacketWrapper.create(ClientboundPackets1_21.UPDATE_ATTRIBUTES, connection); - attributesPacket.write(Types.VAR_INT, efficiency.entityId()); + attributesPacket.write(Types.VAR_INT, enchants.entityId()); - attributesPacket.write(Types.VAR_INT, 1); // Size - attributesPacket.write(Types.VAR_INT, MINING_EFFICIENCY_ID); // Attribute ID - attributesPacket.write(Types.DOUBLE, 0D); // Base + final List list = List.of(enchants.efficiency(), enchants.soulSpeed(), enchants.swiftSneak(), enchants.depthStrider()); + attributesPacket.write(Types.VAR_INT, list.size()); + for (final ActiveEnchant enchant : list) { + final EnchantAttributeModifier modifier = enchant.modifier; + attributesPacket.write(Types.VAR_INT, modifier.attributeId); + attributesPacket.write(Types.DOUBLE, modifier.baseValue); - final int level = efficiency.level; - if (level > 0) { - final double modifierAmount = (level * level) + 1D; - attributesPacket.write(Types.VAR_INT, 1); // Modifiers - attributesPacket.write(Types.STRING, "minecraft:enchantment.efficiency/mainhand"); // Id - attributesPacket.write(Types.DOUBLE, modifierAmount); - attributesPacket.write(Types.BYTE, (byte) 0); // 'Add' operation - } else { - attributesPacket.write(Types.VAR_INT, 0); // Modifiers + if (enchant.level > 0) { + attributesPacket.write(Types.VAR_INT, 1); // Modifiers + attributesPacket.write(Types.STRING, modifier.key); + attributesPacket.write(Types.DOUBLE, enchant.modifier.modifierFunction.get(enchant.level)); + attributesPacket.write(Types.BYTE, (byte) 0); // 'Add' operation + } else { + attributesPacket.write(Types.VAR_INT, 0); // Modifiers + } } attributesPacket.scheduleSend(Protocol1_20_5To1_21.class); } - public record StoredEfficiency(int entityId, int level) { + public record ActiveEnchants(int entityId, ActiveEnchant efficiency, ActiveEnchant soulSpeed, + ActiveEnchant swiftSneak, ActiveEnchant depthStrider) { + } + + public record ActiveEnchant(EnchantAttributeModifier modifier, int level) { + } + + public static final class EnchantAttributeModifier { // Private constructor, equals by reference + private final String key; + private final int attributeId; + private final double baseValue; + private final LevelToModifier modifierFunction; + + private EnchantAttributeModifier(final String key, final int attributeId, final double baseValue, final LevelToModifier modifierFunction) { + this.key = key; + this.attributeId = attributeId; + this.baseValue = baseValue; + this.modifierFunction = modifierFunction; + } + } + + @FunctionalInterface + private interface LevelToModifier { + + double get(int level); } }