diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_21to1_21_2/Protocol1_21To1_21_2.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_21to1_21_2/Protocol1_21To1_21_2.java index 503302e56..89508784d 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/v1_21to1_21_2/Protocol1_21To1_21_2.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_21to1_21_2/Protocol1_21To1_21_2.java @@ -31,7 +31,6 @@ import com.viaversion.viaversion.api.rewriter.ComponentRewriter; import com.viaversion.viaversion.api.type.Types; import com.viaversion.viaversion.api.type.types.misc.ParticleType; import com.viaversion.viaversion.api.type.types.version.Types1_21_2; -import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundConfigurationPackets1_20_5; import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundPacket1_20_5; @@ -49,6 +48,7 @@ import com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.EntityPacketRe import com.viaversion.viaversion.protocols.v1_21to1_21_2.rewriter.ParticleRewriter1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.storage.BundleStateTracker; import com.viaversion.viaversion.protocols.v1_21to1_21_2.storage.ChunkLoadTracker; +import com.viaversion.viaversion.protocols.v1_21to1_21_2.storage.EntityTracker1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.storage.PlayerPositionStorage; import com.viaversion.viaversion.rewriter.AttributeRewriter; import com.viaversion.viaversion.rewriter.SoundRewriter; @@ -230,7 +230,7 @@ public final class Protocol1_21To1_21_2 extends AbstractProtocol { @@ -65,6 +71,32 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter { + final int entityType = wrapper.get(Types.VAR_INT, 1); + + final EntityType type = typeFromId(entityType); + if (type == null || !type.isOrHasParent(EntityTypes1_21_2.ABSTRACT_BOAT)) { + return; + } + + final int entityId = wrapper.get(Types.VAR_INT, 0); + final UUID uuid = wrapper.get(Types.UUID, 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 float pitch = wrapper.get(Types.BYTE, 0) * 256.0F / 360.0F; + final float yaw = wrapper.get(Types.BYTE, 1) * 256.0F / 360.0F; + + final int data = wrapper.get(Types.VAR_INT, 2); + + final EntityTracker1_21_2 tracker = tracker(wrapper.user()); + final EntityTracker1_21_2.BoatEntity entity = tracker.trackBoatEntity(entityId, uuid, data); + entity.setPosition(x, y, z); + entity.setRotation(yaw, pitch); + }); + protocol.registerFinishConfiguration(ClientboundConfigurationPackets1_21.FINISH_CONFIGURATION, wrapper -> { final PacketWrapper instrumentsPacket = wrapper.create(ClientboundConfigurationPackets1_21.REGISTRY_DATA); instrumentsPacket.write(Types.STRING, "minecraft:instrument"); @@ -193,6 +225,12 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter { - wrapper.passthrough(Types.VAR_INT); // Entity ID + final int entityId = wrapper.passthrough(Types.VAR_INT); // Entity ID - wrapper.passthrough(Types.DOUBLE); // X - wrapper.passthrough(Types.DOUBLE); // Y - wrapper.passthrough(Types.DOUBLE); // Z + final double x = wrapper.passthrough(Types.DOUBLE); // X + final double y = wrapper.passthrough(Types.DOUBLE); // Y + final double z = wrapper.passthrough(Types.DOUBLE); // Z // Unused... wrapper.write(Types.DOUBLE, 0D); // Delta movement X @@ -255,11 +293,22 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter storeEntityPositionRotation(wrapper, true, false)); + protocol.registerClientbound(ClientboundPackets1_21.MOVE_ENTITY_POS_ROT, wrapper -> storeEntityPositionRotation(wrapper, true, true)); + protocol.registerClientbound(ClientboundPackets1_21.MOVE_ENTITY_ROT, wrapper -> storeEntityPositionRotation(wrapper, false, true)); protocol.registerServerbound(ServerboundPackets1_21_2.MOVE_PLAYER_POS, wrapper -> { wrapper.passthrough(Types.DOUBLE); // X @@ -326,6 +375,27 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter { + final int dataIndex = event.index(); + // Boat type - now set as own entity type + // Idea is to remove the old entity, then add a new one and re-apply entity data and passengers + if (dataIndex > 11) { + event.setIndex(dataIndex - 1); + return; + } + if (dataIndex != 11) { + return; + } + event.cancel(); + + final EntityTracker1_21_2 tracker = tracker(event.user()); + final EntityTracker1_21_2.BoatEntity entity = tracker.trackedBoatEntity(event.entityId()); + if (entity == null) { + return; + } + + final boolean isBundling = event.user().get(BundleStateTracker.class).isBundling(); + if (!isBundling) { + final PacketWrapper bundleStart = PacketWrapper.create(ClientboundPackets1_21_2.BUNDLE_DELIMITER, event.user()); + bundleStart.send(Protocol1_21To1_21_2.class); + } + + // Remove old entity + final PacketWrapper removeEntityPacket = PacketWrapper.create(ClientboundPackets1_21_2.REMOVE_ENTITIES, event.user()); + removeEntityPacket.write(Types.VAR_INT_ARRAY_PRIMITIVE, new int[] { event.entityId() }); + removeEntityPacket.send(Protocol1_21To1_21_2.class); + + // Detect correct boat entity type from entity data + final int boatType = (int) data.getValue(); + EntityType entityType; + if (tracker.entityType(event.entityId()).isOrHasParent(EntityTypes1_21_2.ABSTRACT_CHEST_BOAT)) { + entityType = entityTypeFromChestBoatType(boatType); + } else { + entityType = entityTypeFromBoatType(boatType); + } + + // Spawn new entity + final PacketWrapper spawnEntityPacket = PacketWrapper.create(ClientboundPackets1_21_2.ADD_ENTITY, event.user()); + spawnEntityPacket.write(Types.VAR_INT, event.entityId()); // Entity ID + spawnEntityPacket.write(Types.UUID, entity.uuid()); // Entity UUID + spawnEntityPacket.write(Types.VAR_INT, entityType.getId()); // Entity type + spawnEntityPacket.write(Types.DOUBLE, entity.x()); // X + spawnEntityPacket.write(Types.DOUBLE, entity.y()); // Y + spawnEntityPacket.write(Types.DOUBLE, entity.z()); // Z + spawnEntityPacket.write(Types.BYTE, (byte) Math.floor(entity.pitch() * 256.0F / 360.0F)); // Pitch + spawnEntityPacket.write(Types.BYTE, (byte) Math.floor(entity.yaw() * 256.0F / 360.0F)); // Yaw + spawnEntityPacket.write(Types.BYTE, (byte) 0); // Head yaw + spawnEntityPacket.write(Types.VAR_INT, entity.data()); // Data + spawnEntityPacket.write(Types.SHORT, (short) 0); // Velocity X + spawnEntityPacket.write(Types.SHORT, (short) 0); // Velocity Y + spawnEntityPacket.write(Types.SHORT, (short) 0); // Velocity Z + spawnEntityPacket.send(Protocol1_21To1_21_2.class); + + // Update tracked entity in storage with new entity type + tracker.updateBoatType(event.entityId(), entityType); + + // Re-apply entity data previously set + final PacketWrapper setEntityDataPacket = PacketWrapper.create(ClientboundPackets1_21_2.SET_ENTITY_DATA, event.user()); + setEntityDataPacket.write(Types.VAR_INT, event.entityId()); + setEntityDataPacket.write(Types1_21_2.ENTITY_DATA_LIST, entity.entityData()); + setEntityDataPacket.send(Protocol1_21To1_21_2.class); + + // Re-attach all passengers + if (entity.passengers() != null) { + final PacketWrapper setPassengersPacket = PacketWrapper.create(ClientboundPackets1_21_2.SET_PASSENGERS, event.user()); + setPassengersPacket.write(Types.VAR_INT, event.entityId()); + setPassengersPacket.write(Types.VAR_INT_ARRAY_PRIMITIVE, entity.passengers()); + setPassengersPacket.send(Protocol1_21To1_21_2.class); + } + + if (!isBundling) { + final PacketWrapper bundleEnd = PacketWrapper.create(ClientboundPackets1_21_2.BUNDLE_DELIMITER, event.user()); + bundleEnd.send(Protocol1_21To1_21_2.class); + } + }); filter().type(EntityTypes1_21_2.SALMON).addIndex(17); // Data type filter().type(EntityTypes1_21_2.AGEABLE_WATER_CREATURE).addIndex(16); // Baby @@ -349,6 +496,76 @@ public final class EntityPacketRewriter1_21_2 extends EntityRewriter dataList, final UserConnection connection) { + super.handleEntityData(entityId, dataList, connection); + + final EntityTracker1_21_2 tracker = tracker(connection); + final EntityType entityType = tracker.entityType(entityId); + if (entityType != null && !entityType.isOrHasParent(EntityTypes1_21_2.ABSTRACT_BOAT)) { + return; + } + + final List entityData = tracker.trackedBoatEntity(entityId).entityData(); + entityData.removeIf(first -> dataList.stream().anyMatch(second -> first.id() == second.id())); + for (final EntityData data : dataList) { + final Object value = data.value(); + if (value instanceof Item item) { + entityData.add(new EntityData(data.id(), data.dataType(), item.copy())); + } else { + entityData.add(new EntityData(data.id(), data.dataType(), value)); + } + } + } + + private EntityType entityTypeFromBoatType(final int boatType) { + if (boatType == 0) { + return EntityTypes1_21_2.OAK_BOAT; + } else if (boatType == 1) { + return EntityTypes1_21_2.SPRUCE_BOAT; + } else if (boatType == 2) { + return EntityTypes1_21_2.BIRCH_BOAT; + } else if (boatType == 3) { + return EntityTypes1_21_2.JUNGLE_BOAT; + } else if (boatType == 4) { + return EntityTypes1_21_2.ACACIA_BOAT; + } else if (boatType == 5) { + return EntityTypes1_21_2.CHERRY_BOAT; + } else if (boatType == 6) { + return EntityTypes1_21_2.DARK_OAK_BOAT; + } else if (boatType == 7) { + return EntityTypes1_21_2.MANGROVE_BOAT; + } else if (boatType == 8) { + return EntityTypes1_21_2.BAMBOO_RAFT; + } else { + return EntityTypes1_21_2.OAK_BOAT; // Fallback + } + } + + private EntityType entityTypeFromChestBoatType(final int chestBoatType) { + if (chestBoatType == 0) { + return EntityTypes1_21_2.OAK_CHEST_BOAT; + } else if (chestBoatType == 1) { + return EntityTypes1_21_2.SPRUCE_CHEST_BOAT; + } else if (chestBoatType == 2) { + return EntityTypes1_21_2.BIRCH_CHEST_BOAT; + } else if (chestBoatType == 3) { + return EntityTypes1_21_2.JUNGLE_CHEST_BOAT; + } else if (chestBoatType == 4) { + return EntityTypes1_21_2.ACACIA_CHEST_BOAT; + } else if (chestBoatType == 5) { + return EntityTypes1_21_2.CHERRY_CHEST_BOAT; + } else if (chestBoatType == 6) { + return EntityTypes1_21_2.DARK_OAK_CHEST_BOAT; + } else if (chestBoatType == 7) { + return EntityTypes1_21_2.MANGROVE_CHEST_BOAT; + } else if (chestBoatType == 8) { + return EntityTypes1_21_2.BAMBOO_CHEST_RAFT; + } else { + return EntityTypes1_21_2.OAK_CHEST_BOAT; // Fallback + } + } + @Override public EntityType typeFromId(final int type) { return EntityTypes1_21_2.getTypeFromId(type); diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_21to1_21_2/storage/EntityTracker1_21_2.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_21to1_21_2/storage/EntityTracker1_21_2.java new file mode 100644 index 000000000..72a312b7a --- /dev/null +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_21to1_21_2/storage/EntityTracker1_21_2.java @@ -0,0 +1,135 @@ +/* + * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion + * Copyright (C) 2016-2024 ViaVersion and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.viaversion.viaversion.protocols.v1_21to1_21_2.storage; + +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.entities.EntityType; +import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_2; +import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; +import com.viaversion.viaversion.data.entity.EntityTrackerBase; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public final class EntityTracker1_21_2 extends EntityTrackerBase { + + private final Int2ObjectMap boats = new Int2ObjectOpenHashMap<>(); + + public EntityTracker1_21_2(final UserConnection connection) { + super(connection, EntityTypes1_21_2.PLAYER); + } + + public BoatEntity trackBoatEntity(final int entityId, final UUID uuid, final int data) { + final BoatEntity entity = new BoatEntity(uuid, data); + boats.put(entityId, entity); + return entity; + } + + public BoatEntity trackedBoatEntity(final int entityId) { + return boats.get(entityId); + } + + @Override + public void removeEntity(final int id) { + super.removeEntity(id); + boats.remove(id); + } + + public void updateBoatType(final int entityId, final EntityType type) { + final BoatEntity entity = boats.get(entityId); + removeEntity(entityId); + boats.put(entityId, entity); + addEntity(entityId, type); + } + + public static class BoatEntity { + + private final List entityData = new ArrayList<>(); + + private final UUID uuid; + private final int data; + + private double x; + private double y; + private double z; + + private float yaw; + private float pitch; + + private int[] passengers; + + public BoatEntity(final UUID uuid, final int data) { + this.uuid = uuid; + this.data = data; + } + + public void setPosition(final double x, final double y, final double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setRotation(final float yaw, final float pitch) { + this.yaw = yaw; + this.pitch = pitch; + } + + public void setPassengers(final int[] passengers) { + this.passengers = passengers; + } + + public UUID uuid() { + return uuid; + } + + public int data() { + return data; + } + + public double x() { + return x; + } + + public double y() { + return y; + } + + public double z() { + return z; + } + + public float yaw() { + return yaw; + } + + public float pitch() { + return pitch; + } + + public List entityData() { + return entityData; + } + + public int[] passengers() { + return passengers; + } + } + +}