Skip to content

Commit ff09769

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents 07dbf68 + dd1fad2 commit ff09769

File tree

14 files changed

+286
-46
lines changed

14 files changed

+286
-46
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ The base ViaVersion jar runs on Paper and Velocity. We also have projects integr
1111
on Fabric, Forge, Bungee, Sponge, or as a standalone proxy to join from basically any client version on
1212
any server version from the past decade. **See [HERE](https://github.com/ViaVersion) for an overview of the different Via\* projects.**
1313

14+
Note that ViaVersion will be able to **run best on either Paper servers or Fabric clients** due to having
15+
direct access to client/server state and more extensive API.
16+
1417
Supported Versions:
1518

1619
![Table (https://i.imgur.com/sTrVnC2.png)](https://i.imgur.com/sTrVnC2.png)
@@ -67,6 +70,8 @@ dependencies {
6770
If you need access to the existing protocol or platform implementations, use the parent artifact `viaversion`.
6871
Please note the [differences in licensing](#license).
6972

73+
Note: If you want to make your own platform implementation of ViaVersion (and additional addons),
74+
you can use the [ViaLoader](https://github.com/ViaVersion/ViaLoader) project.
7075

7176
Building
7277
--------

api/src/main/java/com/viaversion/viaversion/api/minecraft/RegistryEntry.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
import com.viaversion.nbt.tag.Tag;
2626
import org.checkerframework.checker.nullness.qual.Nullable;
2727

28+
/**
29+
* Represents an entry in a registry.
30+
*
31+
* @param key key of the registry entry
32+
* @param tag data of the registry entry, or null if the client should use its default
33+
*/
2834
public record RegistryEntry(String key, @Nullable Tag tag) {
2935

3036
public RegistryEntry withKey(final String key) {

api/src/main/java/com/viaversion/viaversion/api/protocol/version/ProtocolVersionRange.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,10 @@ public ProtocolVersionRange add(final Range<ProtocolVersion> range) {
104104
*/
105105
public boolean contains(final ProtocolVersion version) {
106106
if (this.ranges == null) return true;
107-
return this.ranges.stream().anyMatch(range -> range.contains(version));
107+
for (Range<ProtocolVersion> range : this.ranges) {
108+
if (range.contains(version)) return true;
109+
}
110+
return false;
108111
}
109112

110113
@Override

bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/v1_20_5to1_21/PlayerChangeItemListener.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.bukkit.event.EventPriority;
3232
import org.bukkit.event.player.PlayerItemHeldEvent;
3333
import org.bukkit.inventory.ItemStack;
34+
import org.bukkit.inventory.PlayerInventory;
3435
import org.checkerframework.checker.nullness.qual.Nullable;
3536

3637
/**
@@ -40,6 +41,10 @@
4041
public final class PlayerChangeItemListener extends ViaBukkitListener {
4142

4243
private final Enchantment efficiency = Enchantment.getByKey(NamespacedKey.minecraft("efficiency"));
44+
private final Enchantment aquaAffinity = Enchantment.getByKey(NamespacedKey.minecraft("aqua_affinity"));
45+
private final Enchantment depthStrider = Enchantment.getByKey(NamespacedKey.minecraft("depth_strider"));
46+
private final Enchantment soulSpeed = Enchantment.getByKey(NamespacedKey.minecraft("soul_speed"));
47+
private final Enchantment swiftSneak = Enchantment.getByKey(NamespacedKey.minecraft("swift_sneak"));
4348

4449
public PlayerChangeItemListener(final ViaVersionPlugin plugin) {
4550
super(plugin, Protocol1_20_5To1_21.class);
@@ -49,19 +54,27 @@ public PlayerChangeItemListener(final ViaVersionPlugin plugin) {
4954
public void onPlayerInventorySlotChangedEvent(final PlayerInventorySlotChangeEvent event) {
5055
final Player player = event.getPlayer();
5156
final ItemStack item = event.getNewItemStack();
52-
if (event.getSlot() == player.getInventory().getHeldItemSlot()) {
53-
sendAttributeUpdate(player, item);
57+
final PlayerInventory inventory = player.getInventory();
58+
final int slot = event.getSlot();
59+
if (slot == inventory.getHeldItemSlot()) {
60+
sendAttributeUpdate(player, item, Slot.HAND);
61+
} else if (slot == 36) {
62+
sendAttributeUpdate(player, item, Slot.BOOTS);
63+
} else if (slot == 37) {
64+
sendAttributeUpdate(player, item, Slot.LEGGINGS);
65+
} else if (slot == 39) {
66+
sendAttributeUpdate(player, item, Slot.HELMET);
5467
}
5568
}
5669

5770
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
5871
public void onPlayerItemHeld(final PlayerItemHeldEvent event) {
5972
final Player player = event.getPlayer();
6073
final ItemStack item = player.getInventory().getItem(event.getNewSlot());
61-
sendAttributeUpdate(player, item);
74+
sendAttributeUpdate(player, item, Slot.HAND);
6275
}
6376

64-
private void sendAttributeUpdate(final Player player, @Nullable final ItemStack item) {
77+
private void sendAttributeUpdate(final Player player, @Nullable final ItemStack item, final Slot slot) {
6578
final UserConnection connection = Via.getAPI().getConnection(player.getUniqueId());
6679
if (connection == null || !isOnPipe(player)) {
6780
return;
@@ -72,7 +85,27 @@ private void sendAttributeUpdate(final Player player, @Nullable final ItemStack
7285
return;
7386
}
7487

75-
final int efficiencyLevel = item != null ? item.getEnchantmentLevel(efficiency) : 0;
76-
storage.setEfficiencyLevel(new EfficiencyAttributeStorage.StoredEfficiency(player.getEntityId(), efficiencyLevel), connection);
88+
final EfficiencyAttributeStorage.ActiveEnchants activeEnchants = storage.activeEnchants();
89+
int efficiencyLevel = activeEnchants.efficiency().level();
90+
int aquaAffinityLevel = activeEnchants.aquaAffinity().level();
91+
int soulSpeedLevel = activeEnchants.soulSpeed().level();
92+
int swiftSneakLevel = activeEnchants.swiftSneak().level();
93+
int depthStriderLevel = activeEnchants.depthStrider().level();
94+
switch (slot) {
95+
case HAND -> efficiencyLevel = item != null ? item.getEnchantmentLevel(efficiency) : 0;
96+
case HELMET -> aquaAffinityLevel = item != null ? item.getEnchantmentLevel(aquaAffinity) : 0;
97+
case LEGGINGS -> swiftSneakLevel = item != null && swiftSneak != null ? item.getEnchantmentLevel(swiftSneak) : 0;
98+
case BOOTS -> {
99+
depthStriderLevel = item != null && depthStrider != null ? item.getEnchantmentLevel(depthStrider) : 0;
100+
// TODO This needs continuous ticking for the supporting block as a conditional effect
101+
// and is even more prone to desync from high ping than the other attributes
102+
//soulSpeedLevel = item != null && soulSpeed != null ? item.getEnchantmentLevel(soulSpeed) : 0;
103+
}
104+
}
105+
storage.setEnchants(player.getEntityId(), connection, efficiencyLevel, soulSpeedLevel, swiftSneakLevel, aquaAffinityLevel, depthStriderLevel);
106+
}
107+
108+
private enum Slot {
109+
HAND, BOOTS, LEGGINGS, HELMET
77110
}
78111
}

common/src/main/java/com/viaversion/viaversion/data/entity/DimensionDataImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ public DimensionDataImpl(final int id, final CompoundTag dimensionData) {
4848
this.minY = minY.asInt();
4949
}
5050

51+
public static DimensionData withDefaultsFor(final String key, final int id) {
52+
return switch (key) {
53+
case "overworld", "overworld_caves" -> new DimensionDataImpl(id, -64, 384);
54+
case "the_nether", "the_end" -> new DimensionDataImpl(id, 0, 256);
55+
default -> throw new IllegalArgumentException("Missing registry data for unknown dimension: " + key);
56+
};
57+
}
58+
5159
@Override
5260
public int id() {
5361
return id;

common/src/main/java/com/viaversion/viaversion/protocols/v1_20_3to1_20_5/rewriter/BlockItemPacketRewriter1_20_5.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,18 +225,32 @@ public void registerPackets() {
225225
wrapper.passthrough(Types.DOUBLE); // X
226226
wrapper.passthrough(Types.DOUBLE); // Y
227227
wrapper.passthrough(Types.DOUBLE); // Z
228-
wrapper.passthrough(Types.FLOAT); // Offset X
229-
wrapper.passthrough(Types.FLOAT); // Offset Y
230-
wrapper.passthrough(Types.FLOAT); // Offset Z
228+
final float offX = wrapper.passthrough(Types.FLOAT);
229+
final float offY = wrapper.passthrough(Types.FLOAT);
230+
final float offZ = wrapper.passthrough(Types.FLOAT);
231231
final float data = wrapper.passthrough(Types.FLOAT);
232-
wrapper.passthrough(Types.INT); // Particle Count
232+
final int count = wrapper.passthrough(Types.INT);
233233

234234
// Read data and add it to Particle
235235
final ParticleMappings mappings = protocol.getMappingData().getParticleMappings();
236236
final int mappedId = mappings.getNewId(particleId);
237237
final Particle particle = new Particle(mappedId);
238238
if (mappedId == mappings.mappedId("entity_effect")) {
239-
particle.add(Types.INT, data != 0 ? ThreadLocalRandom.current().nextInt() : 0); // rgb
239+
final int color;
240+
if (data == 0) {
241+
// Black
242+
color = 0;
243+
} else if (count != 0) {
244+
// Randomized color
245+
color = ThreadLocalRandom.current().nextInt();
246+
} else {
247+
// From offset
248+
final int red = Math.round(offX * 255);
249+
final int green = Math.round(offY * 255);
250+
final int blue = Math.round(offZ * 255);
251+
color = (red << 16) | (green << 8) | blue;
252+
}
253+
particle.add(Types.INT, EntityPacketRewriter1_20_5.withAlpha(color));
240254
} else if (particleId == mappings.id("dust_color_transition")) {
241255
for (int i = 0; i < 7; i++) {
242256
particle.add(Types.FLOAT, wrapper.read(Types.FLOAT));
@@ -1075,13 +1089,13 @@ private void updateEffects(final ListTag<CompoundTag> effects, final StructuredD
10751089
data.set(StructuredDataKey.SUSPICIOUS_STEW_EFFECTS, suspiciousStewEffects);
10761090
}
10771091

1078-
private void updateLodestoneTracker(final boolean tracked, final CompoundTag lodestonePosTag, final String lodestoneDimensionTag, final StructuredDataContainer data) {
1092+
private void updateLodestoneTracker(final boolean tracked, final CompoundTag lodestonePosTag, final String lodestoneDimension, final StructuredDataContainer data) {
10791093
GlobalBlockPosition position = null;
1080-
if (lodestonePosTag != null && lodestoneDimensionTag != null) {
1094+
if (lodestonePosTag != null && lodestoneDimension != null) {
10811095
final int x = lodestonePosTag.getInt("X");
10821096
final int y = lodestonePosTag.getInt("Y");
10831097
final int z = lodestonePosTag.getInt("Z");
1084-
position = new GlobalBlockPosition(lodestoneDimensionTag, x, y, z);
1098+
position = new GlobalBlockPosition(lodestoneDimension, x, y, z);
10851099
}
10861100
data.set(StructuredDataKey.LODESTONE_TRACKER, new LodestoneTracker(position, tracked));
10871101
}

common/src/main/java/com/viaversion/viaversion/protocols/v1_20_3to1_20_5/rewriter/EntityPacketRewriter1_20_5.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ private void writeAttribute(final PacketWrapper wrapper, final String attributeI
387387
}
388388
}
389389

390-
private int withAlpha(final int rgb) {
390+
static int withAlpha(final int rgb) {
391391
return 255 << 24 | rgb & 0xffffff;
392392
}
393393

common/src/main/java/com/viaversion/viaversion/protocols/v1_20_5to1_21/storage/EfficiencyAttributeStorage.java

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,57 +23,119 @@
2323
import com.viaversion.viaversion.api.type.Types;
2424
import com.viaversion.viaversion.protocols.v1_20_5to1_21.Protocol1_20_5To1_21;
2525
import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21;
26+
import java.util.List;
2627

2728
public final class EfficiencyAttributeStorage implements StorableObject {
2829

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+
private static final EnchantAttributeModifier EFFICIENCY = new EnchantAttributeModifier("minecraft:enchantment.efficiency/mainhand", 19, 0, level -> (level * level) + 1);
31+
private static final EnchantAttributeModifier SOUL_SPEED = new EnchantAttributeModifier("minecraft:enchantment.soul_speed", 21, 0.1, level -> 0.04D + ((level - 1) * 0.01D));
32+
private static final EnchantAttributeModifier SWIFT_SNEAK = new EnchantAttributeModifier("minecraft:enchantment.swift_sneak", 25, 0.3, level -> level * 0.15D);
33+
private static final EnchantAttributeModifier AQUA_AFFINITY = new EnchantAttributeModifier("minecraft:enchantment.aqua_affinity", 28, 0.2, level -> level * 4, (byte) 2);
34+
private static final EnchantAttributeModifier DEPTH_STRIDER = new EnchantAttributeModifier("minecraft:enchantment.depth_strider", 30, 0, level -> level / 3D);
35+
private static final ActiveEnchants DEFAULT = new ActiveEnchants(-1,
36+
new ActiveEnchant(EFFICIENCY, 0),
37+
new ActiveEnchant(SOUL_SPEED, 0),
38+
new ActiveEnchant(SWIFT_SNEAK, 0),
39+
new ActiveEnchant(AQUA_AFFINITY, 0),
40+
new ActiveEnchant(DEPTH_STRIDER, 0)
41+
);
42+
private final Object lock = new Object();
43+
private volatile boolean attributesSent = true;
3144
private volatile boolean loginSent;
32-
private volatile StoredEfficiency efficiencyLevel;
45+
private ActiveEnchants activeEnchants = DEFAULT;
3346

34-
public void setEfficiencyLevel(final StoredEfficiency efficiencyLevel, final UserConnection connection) {
35-
this.efficiencyLevel = efficiencyLevel;
47+
public void setEnchants(final int entityId, final UserConnection connection, final int efficiency, final int soulSpeed,
48+
final int swiftSneak, final int aquaAffinity, final int depthStrider) {
49+
// Always called from the main thread
50+
if (efficiency == activeEnchants.efficiency.level
51+
&& soulSpeed == activeEnchants.soulSpeed.level
52+
&& swiftSneak == activeEnchants.swiftSneak.level
53+
&& aquaAffinity == activeEnchants.aquaAffinity.level
54+
&& depthStrider == activeEnchants.depthStrider.level) {
55+
return;
56+
}
57+
58+
synchronized (lock) {
59+
this.activeEnchants = new ActiveEnchants(entityId,
60+
new ActiveEnchant(EFFICIENCY, efficiency),
61+
new ActiveEnchant(SOUL_SPEED, soulSpeed),
62+
new ActiveEnchant(SWIFT_SNEAK, swiftSneak),
63+
new ActiveEnchant(AQUA_AFFINITY, aquaAffinity),
64+
new ActiveEnchant(DEPTH_STRIDER, depthStrider)
65+
);
66+
this.attributesSent = false;
67+
}
3668
sendAttributesPacket(connection);
3769
}
3870

71+
public ActiveEnchants activeEnchants() {
72+
return activeEnchants;
73+
}
74+
3975
public void onLoginSent(final UserConnection connection) {
76+
// Always called from the netty thread
4077
this.loginSent = true;
4178
sendAttributesPacket(connection);
4279
}
4380

4481
private void sendAttributesPacket(final UserConnection connection) {
45-
final StoredEfficiency efficiency;
82+
final ActiveEnchants enchants;
4683
synchronized (lock) {
4784
// Older servers (and often Bungee) will send world state packets before sending the login packet
48-
if (!loginSent || efficiencyLevel == null) {
85+
if (!loginSent || attributesSent) {
4986
return;
5087
}
5188

52-
efficiency = efficiencyLevel;
53-
efficiencyLevel = null;
89+
enchants = this.activeEnchants;
90+
attributesSent = true;
5491
}
5592

5693
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
94+
attributesPacket.write(Types.VAR_INT, enchants.entityId());
95+
96+
final List<ActiveEnchant> list = List.of(
97+
enchants.efficiency(),
98+
enchants.soulSpeed(),
99+
enchants.swiftSneak(),
100+
enchants.aquaAffinity(),
101+
enchants.depthStrider()
102+
);
103+
attributesPacket.write(Types.VAR_INT, list.size());
104+
for (final ActiveEnchant enchant : list) {
105+
final EnchantAttributeModifier modifier = enchant.modifier;
106+
attributesPacket.write(Types.VAR_INT, modifier.attributeId);
107+
attributesPacket.write(Types.DOUBLE, modifier.baseValue);
108+
109+
if (enchant.level > 0) {
110+
attributesPacket.write(Types.VAR_INT, 1); // Modifiers
111+
attributesPacket.write(Types.STRING, modifier.key);
112+
attributesPacket.write(Types.DOUBLE, enchant.modifier.modifierFunction.get(enchant.level));
113+
attributesPacket.write(Types.BYTE, modifier.operation);
114+
} else {
115+
attributesPacket.write(Types.VAR_INT, 0); // Modifiers
116+
}
72117
}
73118

74119
attributesPacket.scheduleSend(Protocol1_20_5To1_21.class);
75120
}
76121

77-
public record StoredEfficiency(int entityId, int level) {
122+
public record ActiveEnchants(int entityId, ActiveEnchant efficiency, ActiveEnchant soulSpeed,
123+
ActiveEnchant swiftSneak, ActiveEnchant aquaAffinity, ActiveEnchant depthStrider) {
124+
}
125+
126+
public record ActiveEnchant(EnchantAttributeModifier modifier, int level) {
127+
}
128+
129+
public record EnchantAttributeModifier(String key, int attributeId, double baseValue, LevelToModifier modifierFunction, byte operation) {
130+
131+
private EnchantAttributeModifier(String key, int attributeId, double baseValue, LevelToModifier modifierFunction) {
132+
this(key, attributeId, baseValue, modifierFunction, (byte) 0);
133+
}
134+
}
135+
136+
@FunctionalInterface
137+
private interface LevelToModifier {
138+
139+
double get(int level);
78140
}
79141
}

common/src/main/java/com/viaversion/viaversion/protocols/v1_8to1_9/rewriter/ItemPacketRewriter1_9.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ public void register() {
418418
}
419419

420420
ListTag<StringTag> pages = tag.getListTag("pages", StringTag.class);
421+
tag.put(nbtTagName("pages"), pages == null ? new ListTag<>(StringTag.class) : pages.copy());
422+
421423
if (pages == null) {
422424
pages = new ListTag<>(Collections.singletonList(new StringTag(ComponentUtil.emptyJsonComponent().toString())));
423425
tag.put("pages", pages);
@@ -481,6 +483,32 @@ public void register() {
481483
item.setTag(tag);
482484
item.setData((short) data);
483485
}
486+
if (item.identifier() == 387) { // WRITTEN_BOOK
487+
CompoundTag tag = item.tag();
488+
if (tag != null) {
489+
// Prefer saved pages since they are more likely to be accurate
490+
ListTag<StringTag> backup = tag.removeUnchecked(nbtTagName("pages"));
491+
if (backup != null) {
492+
if (!backup.isEmpty()) {
493+
tag.put("pages", backup);
494+
} else {
495+
tag.remove("pages");
496+
if (tag.isEmpty()) {
497+
item.setTag(null);
498+
}
499+
}
500+
} else {
501+
// Fallback to normal pages tag
502+
ListTag<StringTag> pages = tag.getListTag("pages", StringTag.class);
503+
if (pages != null) {
504+
for (int i = 0; i < pages.size(); i++) {
505+
final StringTag page = pages.get(i);
506+
page.setValue(ComponentUtil.convertJsonOrEmpty(page.getValue(), SerializerVersion.V1_9, SerializerVersion.V1_8).toString());
507+
}
508+
}
509+
}
510+
}
511+
}
484512

485513
boolean newItem = item.identifier() >= 198 && item.identifier() <= 212;
486514
newItem |= item.identifier() == 397 && item.data() == 5;

0 commit comments

Comments
 (0)