Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Johni0702 committed Jun 23, 2024
2 parents 07dbf68 + dd1fad2 commit ff09769
Show file tree
Hide file tree
Showing 14 changed files with 286 additions and 46 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ The base ViaVersion jar runs on Paper and Velocity. We also have projects integr
on Fabric, Forge, Bungee, Sponge, or as a standalone proxy to join from basically any client version on
any server version from the past decade. **See [HERE](https://github.com/ViaVersion) for an overview of the different Via\* projects.**

Note that ViaVersion will be able to **run best on either Paper servers or Fabric clients** due to having
direct access to client/server state and more extensive API.

Supported Versions:

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

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

Building
--------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
import com.viaversion.nbt.tag.Tag;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
* Represents an entry in a registry.
*
* @param key key of the registry entry
* @param tag data of the registry entry, or null if the client should use its default
*/
public record RegistryEntry(String key, @Nullable Tag tag) {

public RegistryEntry withKey(final String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ public ProtocolVersionRange add(final Range<ProtocolVersion> range) {
*/
public boolean contains(final ProtocolVersion version) {
if (this.ranges == null) return true;
return this.ranges.stream().anyMatch(range -> range.contains(version));
for (Range<ProtocolVersion> range : this.ranges) {
if (range.contains(version)) return true;
}
return false;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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;

/**
Expand All @@ -40,6 +41,10 @@
public final class PlayerChangeItemListener extends ViaBukkitListener {

private final Enchantment efficiency = Enchantment.getByKey(NamespacedKey.minecraft("efficiency"));
private final Enchantment aquaAffinity = Enchantment.getByKey(NamespacedKey.minecraft("aqua_affinity"));
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);
Expand All @@ -49,19 +54,27 @@ public PlayerChangeItemListener(final ViaVersionPlugin plugin) {
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);
} else if (slot == 39) {
sendAttributeUpdate(player, item, Slot.HELMET);
}
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
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;
Expand All @@ -72,7 +85,27 @@ private void sendAttributeUpdate(final Player player, @Nullable final ItemStack
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 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);
}

private enum Slot {
HAND, BOOTS, LEGGINGS, HELMET
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ public DimensionDataImpl(final int id, final CompoundTag dimensionData) {
this.minY = minY.asInt();
}

public static DimensionData withDefaultsFor(final String key, final int id) {
return switch (key) {
case "overworld", "overworld_caves" -> new DimensionDataImpl(id, -64, 384);
case "the_nether", "the_end" -> new DimensionDataImpl(id, 0, 256);
default -> throw new IllegalArgumentException("Missing registry data for unknown dimension: " + key);
};
}

@Override
public int id() {
return id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,18 +225,32 @@ public void registerPackets() {
wrapper.passthrough(Types.DOUBLE); // X
wrapper.passthrough(Types.DOUBLE); // Y
wrapper.passthrough(Types.DOUBLE); // Z
wrapper.passthrough(Types.FLOAT); // Offset X
wrapper.passthrough(Types.FLOAT); // Offset Y
wrapper.passthrough(Types.FLOAT); // Offset Z
final float offX = wrapper.passthrough(Types.FLOAT);
final float offY = wrapper.passthrough(Types.FLOAT);
final float offZ = wrapper.passthrough(Types.FLOAT);
final float data = wrapper.passthrough(Types.FLOAT);
wrapper.passthrough(Types.INT); // Particle Count
final int count = wrapper.passthrough(Types.INT);

// Read data and add it to Particle
final ParticleMappings mappings = protocol.getMappingData().getParticleMappings();
final int mappedId = mappings.getNewId(particleId);
final Particle particle = new Particle(mappedId);
if (mappedId == mappings.mappedId("entity_effect")) {
particle.add(Types.INT, data != 0 ? ThreadLocalRandom.current().nextInt() : 0); // rgb
final int color;
if (data == 0) {
// Black
color = 0;
} else if (count != 0) {
// Randomized color
color = ThreadLocalRandom.current().nextInt();
} else {
// From offset
final int red = Math.round(offX * 255);
final int green = Math.round(offY * 255);
final int blue = Math.round(offZ * 255);
color = (red << 16) | (green << 8) | blue;
}
particle.add(Types.INT, EntityPacketRewriter1_20_5.withAlpha(color));
} else if (particleId == mappings.id("dust_color_transition")) {
for (int i = 0; i < 7; i++) {
particle.add(Types.FLOAT, wrapper.read(Types.FLOAT));
Expand Down Expand Up @@ -1075,13 +1089,13 @@ private void updateEffects(final ListTag<CompoundTag> effects, final StructuredD
data.set(StructuredDataKey.SUSPICIOUS_STEW_EFFECTS, suspiciousStewEffects);
}

private void updateLodestoneTracker(final boolean tracked, final CompoundTag lodestonePosTag, final String lodestoneDimensionTag, final StructuredDataContainer data) {
private void updateLodestoneTracker(final boolean tracked, final CompoundTag lodestonePosTag, final String lodestoneDimension, final StructuredDataContainer data) {
GlobalBlockPosition position = null;
if (lodestonePosTag != null && lodestoneDimensionTag != null) {
if (lodestonePosTag != null && lodestoneDimension != null) {
final int x = lodestonePosTag.getInt("X");
final int y = lodestonePosTag.getInt("Y");
final int z = lodestonePosTag.getInt("Z");
position = new GlobalBlockPosition(lodestoneDimensionTag, x, y, z);
position = new GlobalBlockPosition(lodestoneDimension, x, y, z);
}
data.set(StructuredDataKey.LODESTONE_TRACKER, new LodestoneTracker(position, tracked));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ private void writeAttribute(final PacketWrapper wrapper, final String attributeI
}
}

private int withAlpha(final int rgb) {
static int withAlpha(final int rgb) {
return 255 << 24 | rgb & 0xffffff;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,57 +23,119 @@
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
private static final EnchantAttributeModifier EFFICIENCY = new EnchantAttributeModifier("minecraft:enchantment.efficiency/mainhand", 19, 0, level -> (level * level) + 1);
private static final EnchantAttributeModifier SOUL_SPEED = new EnchantAttributeModifier("minecraft:enchantment.soul_speed", 21, 0.1, level -> 0.04D + ((level - 1) * 0.01D));
private static final EnchantAttributeModifier SWIFT_SNEAK = new EnchantAttributeModifier("minecraft:enchantment.swift_sneak", 25, 0.3, level -> level * 0.15D);
private static final EnchantAttributeModifier AQUA_AFFINITY = new EnchantAttributeModifier("minecraft:enchantment.aqua_affinity", 28, 0.2, level -> level * 4, (byte) 2);
private 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(AQUA_AFFINITY, 0),
new ActiveEnchant(DEPTH_STRIDER, 0)
);
private final Object lock = new Object();
private volatile boolean attributesSent = true;
private volatile boolean loginSent;
private volatile StoredEfficiency efficiencyLevel;
private ActiveEnchants activeEnchants = DEFAULT;

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 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;
}

synchronized (lock) {
this.activeEnchants = new ActiveEnchants(entityId,
new ActiveEnchant(EFFICIENCY, efficiency),
new ActiveEnchant(SOUL_SPEED, soulSpeed),
new ActiveEnchant(SWIFT_SNEAK, swiftSneak),
new ActiveEnchant(AQUA_AFFINITY, aquaAffinity),
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, 1); // Size
attributesPacket.write(Types.VAR_INT, MINING_EFFICIENCY_ID); // Attribute ID
attributesPacket.write(Types.DOUBLE, 0D); // Base

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
attributesPacket.write(Types.VAR_INT, enchants.entityId());

final List<ActiveEnchant> list = List.of(
enchants.efficiency(),
enchants.soulSpeed(),
enchants.swiftSneak(),
enchants.aquaAffinity(),
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);

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, modifier.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 aquaAffinity, ActiveEnchant depthStrider) {
}

public record ActiveEnchant(EnchantAttributeModifier modifier, int level) {
}

public record EnchantAttributeModifier(String key, int attributeId, double baseValue, LevelToModifier modifierFunction, byte operation) {

private EnchantAttributeModifier(String key, int attributeId, double baseValue, LevelToModifier modifierFunction) {
this(key, attributeId, baseValue, modifierFunction, (byte) 0);
}
}

@FunctionalInterface
private interface LevelToModifier {

double get(int level);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ public void register() {
}

ListTag<StringTag> pages = tag.getListTag("pages", StringTag.class);
tag.put(nbtTagName("pages"), pages == null ? new ListTag<>(StringTag.class) : pages.copy());

if (pages == null) {
pages = new ListTag<>(Collections.singletonList(new StringTag(ComponentUtil.emptyJsonComponent().toString())));
tag.put("pages", pages);
Expand Down Expand Up @@ -481,6 +483,32 @@ public void register() {
item.setTag(tag);
item.setData((short) data);
}
if (item.identifier() == 387) { // WRITTEN_BOOK
CompoundTag tag = item.tag();
if (tag != null) {
// Prefer saved pages since they are more likely to be accurate
ListTag<StringTag> backup = tag.removeUnchecked(nbtTagName("pages"));
if (backup != null) {
if (!backup.isEmpty()) {
tag.put("pages", backup);
} else {
tag.remove("pages");
if (tag.isEmpty()) {
item.setTag(null);
}
}
} else {
// Fallback to normal pages tag
ListTag<StringTag> pages = tag.getListTag("pages", StringTag.class);
if (pages != null) {
for (int i = 0; i < pages.size(); i++) {
final StringTag page = pages.get(i);
page.setValue(ComponentUtil.convertJsonOrEmpty(page.getValue(), SerializerVersion.V1_9, SerializerVersion.V1_8).toString());
}
}
}
}
}

boolean newItem = item.identifier() >= 198 && item.identifier() <= 212;
newItem |= item.identifier() == 397 && item.data() == 5;
Expand Down
Loading

0 comments on commit ff09769

Please sign in to comment.