diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/Protocol1_19_4To1_19_3.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/Protocol1_19_4To1_19_3.java index 37a873bf..636f248c 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/Protocol1_19_4To1_19_3.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/Protocol1_19_4To1_19_3.java @@ -23,11 +23,10 @@ import com.viaversion.viabackwards.api.rewriters.SoundRewriter; import com.viaversion.viabackwards.api.rewriters.TranslatableRewriter; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter.BlockItemPacketRewriter1_19_4; import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.rewriter.EntityPacketRewriter1_19_4; +import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.storage.EntityTracker1_19_4; import com.viaversion.viaversion.api.connection.UserConnection; -import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.type.Types; -import com.viaversion.viaversion.data.entity.EntityTrackerBase; import com.viaversion.viaversion.libs.gson.JsonElement; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ServerboundPackets1_19_3; @@ -114,7 +113,7 @@ public final class Protocol1_19_4To1_19_3 extends BackwardsProtocol { + final int entityId = wrapper.get(Types.VAR_INT, 0); + final int entityType = wrapper.get(Types.VAR_INT, 1); + + // First track (and remap) entity, then put storage for block display entity + getSpawnTrackerWithDataHandler1_19(EntityTypes1_19_4.FALLING_BLOCK).handle(wrapper); + if (entityType != EntityTypes1_19_4.BLOCK_DISPLAY.getId()) { + return; + } + + final StoredEntityData data = tracker(wrapper.user()).entityData(entityId); + if (data != null) { + final LinkedEntityStorage storage = new LinkedEntityStorage(); + final double x = wrapper.get(Types.DOUBLE, 0); + final double y = wrapper.get(Types.DOUBLE, 1); + final double z = wrapper.get(Types.DOUBLE, 2); + storage.setPosition(x, y, z); + data.put(storage); + } + }); + } + }); + protocol.registerClientbound(ClientboundPackets1_19_4.LOGIN, new PacketHandlers() { @Override public void register() { @@ -137,6 +179,40 @@ public final class EntityPacketRewriter1_19_4 extends EntityRewriter { + final int entityId = wrapper.passthrough(Types.VAR_INT); + final double x = wrapper.passthrough(Types.DOUBLE); + final double y = wrapper.passthrough(Types.DOUBLE); + final double z = wrapper.passthrough(Types.DOUBLE); + + final EntityTracker1_19_4 tracker = tracker(wrapper.user()); + final LinkedEntityStorage storage = tracker.linkedEntityStorage(entityId); + if (storage == null) { + return; + } + storage.setPosition(x, y, z); + }); + + final PacketHandler entityPositionHandler = wrapper -> { + final int entityId = wrapper.passthrough(Types.VAR_INT); + final double x = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; + final double y = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; + final double z = wrapper.passthrough(Types.SHORT) / EntityPositionHandler.RELATIVE_MOVE_FACTOR; + + final EntityTracker1_19_4 tracker = tracker(wrapper.user()); + final LinkedEntityStorage storage = tracker.linkedEntityStorage(entityId); + if (storage == null) { + return; + } + storage.addRelativePosition(x, y, z); + }; + + protocol.registerClientbound(ClientboundPackets1_19_4.MOVE_ENTITY_POS, entityPositionHandler); + protocol.registerClientbound(ClientboundPackets1_19_4.MOVE_ENTITY_POS_ROT, entityPositionHandler); } @Override @@ -168,8 +244,35 @@ public final class EntityPacketRewriter1_19_4 extends EntityRewriter { + final int value = data.value(); + + final EntityTracker1_19_4 tracker = tracker(event.user()); + tracker.clearLinkedEntities(event.entityId()); + + final LinkedEntityStorage storage = tracker.linkedEntityStorage(event.entityId()); + if (storage == null) { + return; + } + final int linkedEntity = tracker.spawnEntity(EntityTypes1_19_3.FALLING_BLOCK, storage.x(), storage.y(), storage.z(), value); + storage.setEntities(linkedEntity); + + final PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_19_3.SET_PASSENGERS, event.user()); + wrapper.write(Types.VAR_INT, event.entityId()); // Entity id + wrapper.write(Types.VAR_INT_ARRAY_PRIMITIVE, new int[] { linkedEntity }); // Passenger entity ids + wrapper.send(Protocol1_19_4To1_19_3.class); + }); + filter().type(EntityTypes1_19_4.ITEM_DISPLAY).index(22).handler((event, data) -> { + final Item value = data.value(); + + final PacketWrapper setEquipment = PacketWrapper.create(ClientboundPackets1_19_3.SET_EQUIPMENT, event.user()); + setEquipment.write(Types.VAR_INT, event.entityId()); // Entity id + setEquipment.write(Types.BYTE, (byte) 5); // Slot - head + setEquipment.write(Types.ITEM1_13_2, value); + + setEquipment.send(Protocol1_19_4To1_19_3.class); + }); filter().type(EntityTypes1_19_4.DISPLAY).handler((event, data) -> { - // TODO Maybe spawn an extra entity to ride the armor stand for blocks and items // Remove a large heap of display entity data if (event.index() > 7) { event.cancel(); diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/storage/EntityTracker1_19_4.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/storage/EntityTracker1_19_4.java new file mode 100644 index 00000000..8449eacc --- /dev/null +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/storage/EntityTracker1_19_4.java @@ -0,0 +1,104 @@ +/* + * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards + * 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.viabackwards.protocol.v1_19_4to1_19_3.storage; + +import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.data.entity.TrackedEntity; +import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3; +import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_4; +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import com.viaversion.viaversion.api.type.Types; +import com.viaversion.viaversion.data.entity.EntityTrackerBase; +import com.viaversion.viaversion.libs.fastutil.ints.IntOpenHashSet; +import com.viaversion.viaversion.libs.fastutil.ints.IntSet; +import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public final class EntityTracker1_19_4 extends EntityTrackerBase { + + private final IntSet generatedEntities = new IntOpenHashSet(); // Track entities spawned to prevent duplicated entity ids + + public EntityTracker1_19_4(final UserConnection connection) { + super(connection, EntityTypes1_19_4.PLAYER); + } + + public int spawnEntity(final EntityTypes1_19_3 entityType, final double x, final double y, final double z, final int data) { + final int entityId = nextEntityId(); + + final PacketWrapper addEntity = PacketWrapper.create(ClientboundPackets1_19_3.ADD_ENTITY, user()); + addEntity.write(Types.VAR_INT, entityId); // Entity id + addEntity.write(Types.UUID, UUID.randomUUID()); // Entity UUID + addEntity.write(Types.VAR_INT, entityType.getId()); // Entity type + addEntity.write(Types.DOUBLE, x); // X + addEntity.write(Types.DOUBLE, y); // Y + addEntity.write(Types.DOUBLE, z); // Z + addEntity.write(Types.BYTE, (byte) 0); // Pitch + addEntity.write(Types.BYTE, (byte) 0); // Yaw + addEntity.write(Types.BYTE, (byte) 0); // Head yaw + addEntity.write(Types.VAR_INT, data); // Data + addEntity.write(Types.SHORT, (short) 0); // Velocity X + addEntity.write(Types.SHORT, (short) 0); // Velocity Y + addEntity.write(Types.SHORT, (short) 0); // Velocity Z + + addEntity.send(Protocol1_19_4To1_19_3.class); + + generatedEntities.add(entityId); + return entityId; + } + + @Override + public void clearEntities() { + for (final int id : entities.keySet()) { + clearLinkedEntities(id); + } + super.clearEntities(); + } + + @Override + public void removeEntity(final int id) { + clearLinkedEntities(id); + super.removeEntity(id); + } + + public void clearLinkedEntities(final int id) { + final LinkedEntityStorage storage = linkedEntityStorage(id); + if (storage != null && storage.entities() != null) { + storage.remove(user()); + generatedEntities.remove(id); + } + } + + public LinkedEntityStorage linkedEntityStorage(final int id) { + final TrackedEntity entity = entity(id); + if (entity != null && entity.hasData()) { + return entity.data().get(LinkedEntityStorage.class); + } + return null; + } + + private int nextEntityId() { + final int entityId = -ThreadLocalRandom.current().nextInt(10_000); + if (generatedEntities.contains(entityId)) { + return nextEntityId(); + } + return entityId; + } + +} diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/storage/LinkedEntityStorage.java b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/storage/LinkedEntityStorage.java new file mode 100644 index 00000000..5e34b51b --- /dev/null +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/v1_19_4to1_19_3/storage/LinkedEntityStorage.java @@ -0,0 +1,46 @@ +/* + * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards + * 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.viabackwards.protocol.v1_19_4to1_19_3.storage; + +import com.viaversion.viabackwards.api.entities.storage.EntityPositionStorage; +import com.viaversion.viabackwards.protocol.v1_19_4to1_19_3.Protocol1_19_4To1_19_3; +import com.viaversion.viaversion.api.connection.StorableObject; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import com.viaversion.viaversion.api.type.Types; +import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3; + +public class LinkedEntityStorage extends EntityPositionStorage implements StorableObject { + + private int[] entities; + + public int[] entities() { + return entities; + } + + public void setEntities(final int... entities) { + this.entities = entities; + } + + public void remove(final UserConnection connection) { + final PacketWrapper wrapper = PacketWrapper.create(ClientboundPackets1_19_3.REMOVE_ENTITIES, connection); + wrapper.write(Types.VAR_INT_ARRAY_PRIMITIVE, entities); + + wrapper.send(Protocol1_19_4To1_19_3.class); + } +}