Compare commits

..
Author SHA1 Message Date
Buriks420 22ea5afea5 Fix armor trim storage use from VB in 1.20.5 component rewriter (#5145) 2026-11-07 10:36:43 +01:00
Nassim Jahnke e7416d749f Write long arrays directly into buffer memory
Long arrays with 32 or more values benefit from being directly written into the buffer's backing memory. Smaller arrays and reads do not

Closes #5143
2026-11-06 20:08:54 +01:00
Nassim Jahnke 94931ee97f Handle timeline particles in registry data 2026-11-06 14:09:28 +01:00
Panda 24b0e620d1 Register entity removal for 1.16.2-1.16.4 servers (#5137) 2026-11-06 13:31:28 +01:00
Florian Reuth c6f5bb3e7e Validate entity status bits in 1.8->1.9 (#5141) 2026-11-06 13:31:12 +01:00
9 changed files with 102 additions and 163 deletions

No files matched your search

@@ -27,11 +27,17 @@ import com.viaversion.viaversion.api.minecraft.codec.Ops;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.Types;
import io.netty.buffer.ByteBuf;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static com.viaversion.viaversion.api.type.types.ArrayType.checkLength;
public class LongArrayType extends Type<long[]> {
private static final VarHandle LONG_BIG_ENDIAN = MethodHandles.byteBufferViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN);
private static final int MIN_BULK_WRITE_LENGTH = 32;
private final int length;
public LongArrayType(final int length) {
@@ -68,6 +74,23 @@ public class LongArrayType extends Type<long[]> {
}
public static void writeFixedLength(final ByteBuf buffer, final long[] object) {
final int len = object.length;
if (len >= MIN_BULK_WRITE_LENGTH && buffer.order() == ByteOrder.BIG_ENDIAN) {
final int bytes = len * Long.BYTES;
buffer.ensureWritable(bytes);
if (buffer.nioBufferCount() == 1) {
// Write directly into the buffer's memory instead of calling writeLong for each
final int writerIndex = buffer.writerIndex();
final ByteBuffer nioBuffer = buffer.internalNioBuffer(writerIndex, bytes);
final int offset = nioBuffer.position();
for (int i = 0; i < len; i++) {
LONG_BIG_ENDIAN.set(nioBuffer, offset + i * Long.BYTES, object[i]);
}
buffer.writerIndex(writerIndex + bytes);
return;
}
}
for (final long l : object) {
buffer.writeLong(l);
}
@@ -27,8 +27,8 @@ public final class DefaultRegistrations {
SharedRegistrations.defaultRegistrations().registrations()
.ranges(EntityRegistrations::entity, ProtocolVersion.v1_13, b -> b
.to(ProtocolVersion.v1_14_4, EntityRegistrations::registerEntityPackets1_13)
.to(ProtocolVersion.v1_16_2, EntityRegistrations::registerEntityPackets1_14_4)
.to(ProtocolVersion.v1_17_1, EntityRegistrations::registerEntityPackets1_16_2)
.to(ProtocolVersion.v1_17, EntityRegistrations::registerEntityPackets1_14_4)
.to(ProtocolVersion.v1_17_1, EntityRegistrations::registerEntityPackets1_17)
.to(ProtocolVersion.v1_19, EntityRegistrations::registerEntityPackets1_17_1)
.to(ProtocolVersion.v1_20_5, EntityRegistrations::registerEntityPackets1_19)
.to(ProtocolVersion.v1_21_4, EntityRegistrations::registerEntityPackets1_20_5)
@@ -39,12 +39,12 @@ final class EntityRegistrations {
}
static <CU extends ClientboundPacketType> void registerEntityPackets1_14_4(final RegistrationContext<CU, ?> ctx, final EntityRewriter<CU, ?> er) {
ctx.clientbound(ClientboundPackets1_14_4.REMOVE_ENTITIES, er::registerRemoveEntities);
ctx.clientbound(ClientboundPackets1_14_4.REMOVE_ENTITIES, er::registerRemoveEntities, PacketBound.REMOVED_AT_MAX);
ctx.clientbound(ClientboundPackets1_14_4.ADD_ENTITY, er::registerTrackerWithData);
ctx.clientbound(ClientboundPackets1_14_4.ADD_MOB, er::registerTracker);
}
static <CU extends ClientboundPacketType> void registerEntityPackets1_16_2(final RegistrationContext<CU, ?> ctx, final EntityRewriter<CU, ?> er) {
static <CU extends ClientboundPacketType> void registerEntityPackets1_17(final RegistrationContext<CU, ?> ctx, final EntityRewriter<CU, ?> er) {
// remove_entities got temporarily changed to only take a single entity...
ctx.clientbound(ClientboundPackets1_14_4.ADD_ENTITY, er::registerTrackerWithData);
ctx.clientbound(ClientboundPackets1_14_4.ADD_MOB, er::registerTracker);
@@ -966,8 +966,7 @@ public class ComponentRewriter1_20_5<C extends ClientboundPacketType> extends Js
protected CompoundTag trimToTag(final UserConnection connection, final ArmorTrim1_20_5 value) {
final CompoundTag tag = new CompoundTag();
final Holder<ArmorTrimMaterial1_20_5> material = value.material();
final ProtocolStorables1_20_5 storables = connection.storables(protocol);
final ArmorTrimStorage trimStorage = storables.armorTrims();
final ArmorTrimStorage trimStorage = connection.storables(protocol) instanceof ProtocolStorables1_20_5 storables ? storables.armorTrims() : connection.get(ArmorTrimStorage.class);
if (material.hasId()) {
final String trimMaterial = trimStorage.trimMaterials().idToKey(material.id());
tag.putString("material", trimMaterial);
@@ -1026,8 +1025,7 @@ public class ComponentRewriter1_20_5<C extends ClientboundPacketType> extends Js
final Tag materialTag = value.get("material");
Holder<ArmorTrimMaterial1_20_5> material;
final ProtocolStorables1_20_5 storables = connection.storables(protocol);
final ArmorTrimStorage trimStorage = storables.armorTrims();
final ArmorTrimStorage trimStorage = connection.storables(protocol) instanceof ProtocolStorables1_20_5 storables ? storables.armorTrims() : connection.get(ArmorTrimStorage.class);
if (materialTag instanceof StringTag stringTag) {
material = Holder.of(trimStorage.trimMaterials().keyToId(stringTag.getValue()));
} else {
@@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableList;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.minecraft.EulerAngle;
import com.viaversion.viaversion.api.minecraft.Vector;
import com.viaversion.viaversion.api.minecraft.Vector3d;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_9;
import com.viaversion.viaversion.api.minecraft.entitydata.EntityData;
@@ -113,15 +112,6 @@ public class EntityPacketRewriter1_9 extends EntityRewriter<ClientboundPackets1_
map(Types.BOOLEAN); // 6 - On Ground
handler(wrapper -> {
final int entityId = wrapper.get(Types.VAR_INT, 0);
final double x = wrapper.get(Types.DOUBLE, 0);
final double y = wrapper.get(Types.DOUBLE, 1);
final double z = wrapper.get(Types.DOUBLE, 2);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
tracker.setBossPosition(entityId, new Vector3d(x, y, z));
});
handler(wrapper -> {
int entityID = wrapper.get(Types.VAR_INT, 0);
if (Via.getConfig().isHologramPatch()) {
@@ -150,7 +140,6 @@ public class EntityPacketRewriter1_9 extends EntityRewriter<ClientboundPackets1_
map(Types.BYTE); // 5 - Pitch
map(Types.BOOLEAN); // 6 - On Ground
handler(wrapper -> moveBoss(wrapper, protocol));
}
});
protocol.registerClientbound(ClientboundPackets1_8.MOVE_ENTITY_POS, new PacketHandlers() {
@@ -163,7 +152,6 @@ public class EntityPacketRewriter1_9 extends EntityRewriter<ClientboundPackets1_
map(Types.BYTE, toNewShort); // 3 - Z
map(Types.BOOLEAN); // 4 - On Ground
handler(wrapper -> moveBoss(wrapper, protocol));
}
});
protocol.registerClientbound(ClientboundPackets1_8.SET_EQUIPPED_ITEM, new PacketHandlers() {
@@ -391,17 +379,6 @@ public class EntityPacketRewriter1_9 extends EntityRewriter<ClientboundPackets1_
});
}
private static void moveBoss(PacketWrapper wrapper, Protocol1_8To1_9 protocol) {
// Deltas were already converted to 1.9 units (1/4096 of a block)
final int entityId = wrapper.get(Types.VAR_INT, 0);
final double deltaX = wrapper.get(Types.SHORT, 0) / 4096D;
final double deltaY = wrapper.get(Types.SHORT, 1) / 4096D;
final double deltaZ = wrapper.get(Types.SHORT, 2) / 4096D;
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
tracker.moveBoss(entityId, deltaX, deltaY, deltaZ);
}
@Override
protected void registerRewrites() {
filter().handler(this::handleEntityData);
@@ -434,6 +411,10 @@ public class EntityPacketRewriter1_9 extends EntityRewriter<ClientboundPackets1_
if (dataIndex.getOldType() == EntityDataTypes1_8.INT) {
data.setValue(((Integer) value).byteValue());
}
if (dataIndex == EntityDataIndex1_9.ENTITY_STATUS) {
// 1.8 clients ignore bits 6 and 7, but 1.9+ clients render them as glowing/elytra flying
data.setValue((byte) ((Byte) value & ~0xC0));
}
// After writing the last one
if (dataIndex == EntityDataIndex1_9.ENTITY_STATUS && type == EntityTypes1_9.EntityType.PLAYER) {
byte val = 0;
@@ -25,7 +25,6 @@ import com.viaversion.nbt.tag.StringTag;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.minecraft.ClientWorld;
import com.viaversion.viaversion.api.minecraft.GameMode;
import com.viaversion.viaversion.api.minecraft.Vector3d;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_9;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.api.protocol.remapper.PacketHandler;
@@ -112,17 +111,6 @@ public class PlayerPacketRewriter1_9 {
map(Types.BYTE); // 5 - Player Flags
handler(wrapper -> {
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
final Vector3d position = tracker.getPlayerPosition();
final byte flags = wrapper.get(Types.BYTE, 0);
final double x = wrapper.get(Types.DOUBLE, 0) + ((flags & 0x01) != 0 ? position.x() : 0);
final double y = wrapper.get(Types.DOUBLE, 1) + ((flags & 0x02) != 0 ? position.y() : 0);
final double z = wrapper.get(Types.DOUBLE, 2) + ((flags & 0x04) != 0 ? position.z() : 0);
tracker.setPlayerPosition(new Vector3d(x, y, z));
});
create(Types.VAR_INT, 0); // 6 - Teleport ID was added
}
});
@@ -461,10 +449,6 @@ public class PlayerPacketRewriter1_9 {
tracker.incrementIdlePacket();
tracker.setGround(wrapper.get(Types.BOOLEAN, 0));
};
final PacketHandler positionHandler = wrapper -> {
EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
tracker.setPlayerPosition(new Vector3d(wrapper.get(Types.DOUBLE, 0), wrapper.get(Types.DOUBLE, 1), wrapper.get(Types.DOUBLE, 2)));
};
protocol.registerServerbound(ServerboundPackets1_9.MOVE_PLAYER_POS, new PacketHandlers() {
@Override
public void register() {
@@ -473,7 +457,6 @@ public class PlayerPacketRewriter1_9 {
map(Types.DOUBLE); // 2 - Z
map(Types.BOOLEAN); // 3 - Ground
handler(onGroundHandler);
handler(positionHandler);
}
});
protocol.registerServerbound(ServerboundPackets1_9.MOVE_PLAYER_POS_ROT, new PacketHandlers() {
@@ -486,7 +469,6 @@ public class PlayerPacketRewriter1_9 {
map(Types.FLOAT); // 4 - Pitch
map(Types.BOOLEAN); // 5 - Ground
handler(onGroundHandler);
handler(positionHandler);
}
});
protocol.registerServerbound(ServerboundPackets1_9.MOVE_PLAYER_ROT, new PacketHandlers() {
@@ -18,7 +18,6 @@
package com.viaversion.viaversion.protocols.v1_8to1_9.rewriter;
import com.viaversion.viaversion.api.data.entity.EntityTracker;
import com.viaversion.viaversion.api.minecraft.Vector3d;
import com.viaversion.viaversion.api.minecraft.entities.EntityType;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_8;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_9;
@@ -199,17 +198,6 @@ public class SpawnPacketRewriter1_9 {
map(Types.SHORT); // 10 - Velocity Y
map(Types.SHORT); // 11 - Velocity Z
// Has to happen before the entity data is handled, which may create the boss bar
handler(wrapper -> {
final int entityId = wrapper.get(Types.VAR_INT, 0);
final double x = wrapper.get(Types.DOUBLE, 0);
final double y = wrapper.get(Types.DOUBLE, 1);
final double z = wrapper.get(Types.DOUBLE, 2);
final EntityTracker1_9 tracker = wrapper.user().getEntityTracker(protocol);
tracker.setBossPosition(entityId, new Vector3d(x, y, z));
});
map(Types.ENTITY_DATA_LIST1_8, Types.ENTITY_DATA_LIST1_9);
handler(wrapper -> {
List<EntityData> entityDataList = wrapper.get(Types.ENTITY_DATA_LIST1_9, 0);
@@ -26,7 +26,6 @@ import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import com.viaversion.viaversion.api.minecraft.BlockPosition;
import com.viaversion.viaversion.api.minecraft.GameMode;
import com.viaversion.viaversion.api.minecraft.Vector3d;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_9.EntityType;
import com.viaversion.viaversion.api.minecraft.entitydata.EntityData;
import com.viaversion.viaversion.api.minecraft.entitydata.types.EntityDataTypes1_9;
@@ -59,15 +58,9 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class EntityTracker1_9 extends EntityTrackerBase {
public static final String WITHER_TRANSLATABLE = "{\"translate\":\"entity.WitherBoss.name\"}";
public static final String DRAGON_TRANSLATABLE = "{\"translate\":\"entity.EnderDragon.name\"}";
// 1.8 Entity#isInRangeToRenderDist: average bounding box edge length * 64
private static final double WITHER_RENDER_DISTANCE = (0.9 + 3.5 + 0.9) / 3 * 64;
private static final double DRAGON_RENDER_DISTANCE = (16.0 + 8.0 + 16.0) / 3 * 64;
private final Int2ObjectMap<UUID> uuidMap = new Int2ObjectOpenHashMap<>();
private final Int2IntMap vehicleMap = new Int2IntOpenHashMap();
private final Int2ObjectMap<BossBar> bossBarMap = new Int2ObjectOpenHashMap<>();
private final Int2ObjectMap<Vector3d> bossPositions = new Int2ObjectOpenHashMap<>();
private BossBar shownBossBar;
private Vector3d playerPosition = Vector3d.ZERO;
private final IntSet validBlocking = new IntOpenHashSet();
private final IntSet knownHolograms = new IntOpenHashSet();
private final Set<BlockPosition> blockInteractions = Collections.newSetFromMap(CacheBuilder.newBuilder()
@@ -150,92 +143,16 @@ public class EntityTracker1_9 extends EntityTrackerBase {
validBlocking.remove(entityId);
knownHolograms.remove(entityId);
this.bossPositions.remove(entityId);
BossBar bar = this.bossBarMap.remove(entityId);
if (bar != null && bar == this.shownBossBar) {
this.updateShownBossBar();
BossBar bar = bossBarMap.remove(entityId);
if (bar != null) {
bar.hide();
// Send to provider
Via.getManager().getProviders().get(BossBarProvider.class).handleRemove(user(), bar.getId());
}
return super.removeEntity(entityId);
}
public void setBossPosition(final int entityId, final Vector3d position) {
final com.viaversion.viaversion.api.minecraft.entities.EntityType type = this.entityType(entityId);
if (type != EntityType.WITHER && type != EntityType.ENDER_DRAGON) {
return;
}
this.bossPositions.put(entityId, position);
this.updateShownBossBar();
}
public void moveBoss(final int entityId, final double deltaX, final double deltaY, final double deltaZ) {
final Vector3d position = this.bossPositions.get(entityId);
if (position != null) {
this.setBossPosition(entityId, new Vector3d(position.x() + deltaX, position.y() + deltaY, position.z() + deltaZ));
}
}
public Vector3d getPlayerPosition() {
return this.playerPosition;
}
public void setPlayerPosition(final Vector3d playerPosition) {
this.playerPosition = playerPosition;
this.updateShownBossBar();
}
private void updateShownBossBar() {
BossBar best = null;
int bestSectionDistance = -1;
for (final Int2ObjectMap.Entry<BossBar> entry : this.bossBarMap.int2ObjectEntrySet()) {
final Vector3d position = this.bossPositions.get(entry.getIntKey());
if (position == null) {
continue;
}
final double deltaX = position.x() - this.playerPosition.x();
final double deltaY = position.y() - this.playerPosition.y();
final double deltaZ = position.z() - this.playerPosition.z();
final double range = this.entityType(entry.getIntKey()) == EntityType.ENDER_DRAGON ? DRAGON_RENDER_DISTANCE : WITHER_RENDER_DISTANCE;
if (deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ >= range * range) {
continue;
}
final int distanceX = Math.abs(sectionCoord(position.x()) - sectionCoord(this.playerPosition.x()));
final int distanceY = Math.abs(sectionCoord(position.y()) - sectionCoord(this.playerPosition.y()));
final int distanceZ = Math.abs(sectionCoord(position.z()) - sectionCoord(this.playerPosition.z()));
final int sectionDistance = distanceX + distanceY + distanceZ;
// Prefer the currently shown bar on ties to avoid needless switching
if (sectionDistance > bestSectionDistance || (sectionDistance == bestSectionDistance && entry.getValue() == shownBossBar)) {
best = entry.getValue();
bestSectionDistance = sectionDistance;
}
}
this.showBossBar(best);
}
private static int sectionCoord(double coord) {
return (int) Math.floor(coord) >> 4;
}
private void showBossBar(@Nullable final BossBar bar) {
if (bar == this.shownBossBar) {
return;
}
final BossBarProvider provider = Via.getManager().getProviders().get(BossBarProvider.class);
if (this.shownBossBar != null) {
this.shownBossBar.removeConnection(user());
provider.handleRemove(user(), this.shownBossBar.getId());
}
this.shownBossBar = bar;
if (bar != null) {
bar.addConnection(user());
provider.handleAdd(user(), bar.getId());
}
}
public boolean interactedBlockRecently(final int x, final int y, final int z) {
for (final BlockPosition position : blockInteractions) {
if (Math.abs(position.x() - x) <= 1 && Math.abs(position.y() - y) <= 1 && Math.abs(position.z() - z) <= 1) {
@@ -336,7 +253,11 @@ public class EntityTracker1_9 extends EntityTrackerBase {
if (bar == null) {
bar = Via.getAPI().legacyAPI().createLegacyBossBar(title, BossColor.PINK, BossStyle.SOLID);
bossBarMap.put(entityId, bar);
updateShownBossBar();
bar.addConnection(user());
bar.show();
// Send to provider
Via.getManager().getProviders().get(BossBarProvider.class).handleAdd(user(), bar.getId());
} else {
bar.setTitle(title);
}
@@ -349,7 +270,10 @@ public class EntityTracker1_9 extends EntityTrackerBase {
String title = type == EntityType.ENDER_DRAGON ? DRAGON_TRANSLATABLE : WITHER_TRANSLATABLE;
bar = Via.getAPI().legacyAPI().createLegacyBossBar(title, health, BossColor.PINK, BossStyle.SOLID);
bossBarMap.put(entityId, bar);
updateShownBossBar();
bar.addConnection(user());
bar.show();
// Send to provider
Via.getManager().getProviders().get(BossBarProvider.class).handleAdd(user(), bar.getId());
} else {
bar.setHealth(health);
}
@@ -93,6 +93,7 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
case "worldgen/biome" -> updateBiomes(entries);
case "worldgen/block_state_provider" -> updateBlockStateProviders(entries);
case "dimension_type" -> updateDimensionTypes(entries);
case "timeline" -> updateTimelines(entries);
case "dialog" -> updateDialogs(connection, entries);
}
@@ -459,18 +460,7 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
if (ambientParticles instanceof CompoundTag modifierTag) {
ambientParticles = modifierTag.get("argument");
}
if (ambientParticles instanceof ListTag<?> ambientParticlesList) {
for (final Tag ambientParticle : ambientParticlesList) {
if (!(ambientParticle instanceof CompoundTag ambientParticleTag)) {
continue;
}
final CompoundTag particle = ambientParticleTag.getCompoundTag("particle");
if (particle != null) {
handleParticleData(particle);
}
}
}
handleAmbientParticles(ambientParticles);
Tag dripstoneParticle = TagUtil.getNamespacedTag(tag, "visual/default_dripstone_particle");
if (dripstoneParticle instanceof CompoundTag modifierTag && modifierTag.contains("argument")) {
@@ -481,6 +471,59 @@ public class RegistryDataRewriter implements com.viaversion.viaversion.api.rewri
}
}
private void handleAmbientParticles(final @Nullable Tag ambientParticles) {
if (!(ambientParticles instanceof ListTag<?> ambientParticlesList)) {
return;
}
for (final Tag ambientParticle : ambientParticlesList) {
if (!(ambientParticle instanceof CompoundTag ambientParticleTag)) {
continue;
}
final CompoundTag particle = ambientParticleTag.getCompoundTag("particle");
if (particle != null) {
handleParticleData(particle);
}
}
}
public void updateTimelines(final RegistryEntry[] entries) {
final MappingData mappings = protocol.getMappingData();
if (mappings == null) {
return;
}
for (final RegistryEntry entry : entries) {
if (entry.tag() == null) {
continue;
}
final CompoundTag tracks = ((CompoundTag) entry.tag()).getCompoundTag("tracks");
if (tracks == null) {
continue;
}
if (mappings.changedEnvironmentAttributes() != null) {
for (final String attribute : mappings.changedEnvironmentAttributes()) {
TagUtil.removeNamespaced(tracks, attribute);
}
}
final CompoundTag ambientParticlesTrack = TagUtil.getNamespacedCompoundTag(tracks, "visual/ambient_particles");
if (ambientParticlesTrack == null) {
continue;
}
final ListTag<CompoundTag> keyframes = ambientParticlesTrack.getListTag("keyframes", CompoundTag.class);
if (keyframes != null) {
for (final CompoundTag keyframe : keyframes) {
handleAmbientParticles(keyframe.get("value"));
}
}
}
}
private void updateNestedEffect(final CompoundTag effectsTag) {
final CompoundTag effect = effectsTag.getCompoundTag("effect");
if (effect != null) {