|
23 | 23 | import com.viaversion.viaversion.api.type.Types; |
24 | 24 | import com.viaversion.viaversion.protocols.v1_20_5to1_21.Protocol1_20_5To1_21; |
25 | 25 | import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21; |
| 26 | +import java.util.List; |
26 | 27 |
|
27 | 28 | public final class EfficiencyAttributeStorage implements StorableObject { |
28 | 29 |
|
29 | | - private static final int MINING_EFFICIENCY_ID = 19; |
30 | | - private final Object lock = new Object(); // Slightly sloppy locking, but should be good enough |
| 30 | + public static final EnchantAttributeModifier EFFICIENCY = new EnchantAttributeModifier("minecraft:enchantment.efficiency/mainhand", 19, 0, level -> (level * level) + 1); |
| 31 | + public static final EnchantAttributeModifier SOUL_SPEED = new EnchantAttributeModifier("minecraft:enchantment.soul_speed", 21, 0.1, level -> 0.04D + ((level - 1) * 0.01D)); |
| 32 | + public static final EnchantAttributeModifier SWIFT_SNEAK = new EnchantAttributeModifier("minecraft:enchantment.swift_sneak", 25, 0.3, level -> level * 0.15D); |
| 33 | + public static final EnchantAttributeModifier DEPTH_STRIDER = new EnchantAttributeModifier("minecraft:enchantment.depth_strider", 30, 0, level -> level / 3D); |
| 34 | + private static final ActiveEnchants DEFAULT = new ActiveEnchants(-1, |
| 35 | + new ActiveEnchant(EFFICIENCY, 0), |
| 36 | + new ActiveEnchant(SOUL_SPEED, 0), |
| 37 | + new ActiveEnchant(SWIFT_SNEAK, 0), |
| 38 | + new ActiveEnchant(DEPTH_STRIDER, 0) |
| 39 | + ); |
| 40 | + private final Object lock = new Object(); |
| 41 | + private volatile ActiveEnchants activeEnchants = DEFAULT; |
| 42 | + private volatile boolean attributesSent = true; |
31 | 43 | private volatile boolean loginSent; |
32 | | - private volatile StoredEfficiency efficiencyLevel; |
33 | 44 |
|
34 | | - public void setEfficiencyLevel(final StoredEfficiency efficiencyLevel, final UserConnection connection) { |
35 | | - this.efficiencyLevel = efficiencyLevel; |
| 45 | + public void setEnchants(final int entityId, final UserConnection connection, final int efficiency, final int soulSpeed, final int swiftSneak, final int depthStrider) { |
| 46 | + // Always called from the main thread |
| 47 | + if (efficiency == activeEnchants.efficiency.level |
| 48 | + && soulSpeed == activeEnchants.soulSpeed.level |
| 49 | + && swiftSneak == activeEnchants.swiftSneak.level |
| 50 | + && depthStrider == activeEnchants.depthStrider.level) { |
| 51 | + return; |
| 52 | + } |
| 53 | + |
| 54 | + synchronized (lock) { |
| 55 | + this.activeEnchants = new ActiveEnchants(entityId, |
| 56 | + new ActiveEnchant(EFFICIENCY, efficiency), |
| 57 | + new ActiveEnchant(SOUL_SPEED, soulSpeed), |
| 58 | + new ActiveEnchant(SWIFT_SNEAK, swiftSneak), |
| 59 | + new ActiveEnchant(DEPTH_STRIDER, depthStrider) |
| 60 | + ); |
| 61 | + this.attributesSent = false; |
| 62 | + } |
36 | 63 | sendAttributesPacket(connection); |
37 | 64 | } |
38 | 65 |
|
| 66 | + public ActiveEnchants activeEnchants() { |
| 67 | + return activeEnchants; |
| 68 | + } |
| 69 | + |
39 | 70 | public void onLoginSent(final UserConnection connection) { |
| 71 | + // Always called from the netty thread |
40 | 72 | this.loginSent = true; |
41 | 73 | sendAttributesPacket(connection); |
42 | 74 | } |
43 | 75 |
|
44 | 76 | private void sendAttributesPacket(final UserConnection connection) { |
45 | | - final StoredEfficiency efficiency; |
| 77 | + final ActiveEnchants enchants; |
46 | 78 | synchronized (lock) { |
47 | 79 | // Older servers (and often Bungee) will send world state packets before sending the login packet |
48 | | - if (!loginSent || efficiencyLevel == null) { |
| 80 | + if (!loginSent || attributesSent) { |
49 | 81 | return; |
50 | 82 | } |
51 | 83 |
|
52 | | - efficiency = efficiencyLevel; |
53 | | - efficiencyLevel = null; |
| 84 | + enchants = this.activeEnchants; |
| 85 | + attributesSent = true; |
54 | 86 | } |
55 | 87 |
|
56 | 88 | final PacketWrapper attributesPacket = PacketWrapper.create(ClientboundPackets1_21.UPDATE_ATTRIBUTES, connection); |
57 | | - attributesPacket.write(Types.VAR_INT, efficiency.entityId()); |
58 | | - |
59 | | - attributesPacket.write(Types.VAR_INT, 1); // Size |
60 | | - attributesPacket.write(Types.VAR_INT, MINING_EFFICIENCY_ID); // Attribute ID |
61 | | - attributesPacket.write(Types.DOUBLE, 0D); // Base |
62 | | - |
63 | | - final int level = efficiency.level; |
64 | | - if (level > 0) { |
65 | | - final double modifierAmount = (level * level) + 1D; |
66 | | - attributesPacket.write(Types.VAR_INT, 1); // Modifiers |
67 | | - attributesPacket.write(Types.STRING, "minecraft:enchantment.efficiency/mainhand"); // Id |
68 | | - attributesPacket.write(Types.DOUBLE, modifierAmount); |
69 | | - attributesPacket.write(Types.BYTE, (byte) 0); // 'Add' operation |
70 | | - } else { |
71 | | - attributesPacket.write(Types.VAR_INT, 0); // Modifiers |
| 89 | + attributesPacket.write(Types.VAR_INT, enchants.entityId()); |
| 90 | + |
| 91 | + final List<ActiveEnchant> list = List.of(enchants.efficiency(), enchants.soulSpeed(), enchants.swiftSneak(), enchants.depthStrider()); |
| 92 | + attributesPacket.write(Types.VAR_INT, list.size()); |
| 93 | + for (final ActiveEnchant enchant : list) { |
| 94 | + final EnchantAttributeModifier modifier = enchant.modifier; |
| 95 | + attributesPacket.write(Types.VAR_INT, modifier.attributeId); |
| 96 | + attributesPacket.write(Types.DOUBLE, modifier.baseValue); |
| 97 | + |
| 98 | + if (enchant.level > 0) { |
| 99 | + attributesPacket.write(Types.VAR_INT, 1); // Modifiers |
| 100 | + attributesPacket.write(Types.STRING, modifier.key); |
| 101 | + attributesPacket.write(Types.DOUBLE, enchant.modifier.modifierFunction.get(enchant.level)); |
| 102 | + attributesPacket.write(Types.BYTE, (byte) 0); // 'Add' operation |
| 103 | + } else { |
| 104 | + attributesPacket.write(Types.VAR_INT, 0); // Modifiers |
| 105 | + } |
72 | 106 | } |
73 | 107 |
|
74 | 108 | attributesPacket.scheduleSend(Protocol1_20_5To1_21.class); |
75 | 109 | } |
76 | 110 |
|
77 | | - public record StoredEfficiency(int entityId, int level) { |
| 111 | + public record ActiveEnchants(int entityId, ActiveEnchant efficiency, ActiveEnchant soulSpeed, |
| 112 | + ActiveEnchant swiftSneak, ActiveEnchant depthStrider) { |
| 113 | + } |
| 114 | + |
| 115 | + public record ActiveEnchant(EnchantAttributeModifier modifier, int level) { |
| 116 | + } |
| 117 | + |
| 118 | + public static final class EnchantAttributeModifier { // Private constructor, equals by reference |
| 119 | + private final String key; |
| 120 | + private final int attributeId; |
| 121 | + private final double baseValue; |
| 122 | + private final LevelToModifier modifierFunction; |
| 123 | + |
| 124 | + private EnchantAttributeModifier(final String key, final int attributeId, final double baseValue, final LevelToModifier modifierFunction) { |
| 125 | + this.key = key; |
| 126 | + this.attributeId = attributeId; |
| 127 | + this.baseValue = baseValue; |
| 128 | + this.modifierFunction = modifierFunction; |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + @FunctionalInterface |
| 133 | + private interface LevelToModifier { |
| 134 | + |
| 135 | + double get(int level); |
78 | 136 | } |
79 | 137 | } |
0 commit comments