diff --git a/src/main/java/net/raphimc/viabedrock/api/model/entity/ArmadilloEntity.java b/src/main/java/net/raphimc/viabedrock/api/model/entity/ArmadilloEntity.java new file mode 100644 index 000000000..89edc96e7 --- /dev/null +++ b/src/main/java/net/raphimc/viabedrock/api/model/entity/ArmadilloEntity.java @@ -0,0 +1,93 @@ +/* + * This file is part of ViaBedrock - https://github.com/ViaVersionAddons/ViaBedrock + * Copyright (C) 2023-2026 RK_01 + * + * 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 net.raphimc.viabedrock.api.model.entity; + +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.entities.EntityTypes26_3; +import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; +import com.viaversion.viaversion.api.type.types.version.VersionedTypes; +import net.raphimc.viabedrock.ViaBedrock; +import net.raphimc.viabedrock.api.util.PacketFactory; +import net.raphimc.viabedrock.protocol.data.enums.java.EntityEvent; +import net.raphimc.viabedrock.protocol.data.generated.java.EntityDataFields; + +import java.util.List; +import java.util.UUID; + +public class ArmadilloEntity extends MobEntity { + + private static final int IDLE = 0; + private static final int ROLLING = 1; + private static final int SCARED = 2; + private static final int UNROLLING = 3; + private static final int ROLL_DURATION_TICKS = 10; + + private String bedrockState; + private int javaState = -1; + private int rollingTicks; + private boolean pendingPeek; + + public ArmadilloEntity(final UserConnection user, final long uniqueId, final long runtimeId, final String type, final int javaId, final UUID javaUuid) { + super(user, uniqueId, runtimeId, type, javaId, javaUuid, EntityTypes26_3.ARMADILLO); + } + + public void translateState(final String state, final List javaEntityData) { + if (state.equals(this.bedrockState)) { + return; + } + + final int javaState = switch (state) { + case "unrolled" -> IDLE; + case "rolled_up" -> "unrolled".equals(this.bedrockState) ? ROLLING : SCARED; + case "rolled_up_peeking", "rolled_up_relaxing" -> SCARED; + case "rolled_up_unrolling" -> UNROLLING; + default -> -1; + }; + if (javaState == -1) { + ViaBedrock.getPlatform().getLogger().warning("Received unknown armadillo state " + state); + return; + } + + this.bedrockState = state; + this.pendingPeek = state.equals("rolled_up_peeking"); + this.rollingTicks = javaState == ROLLING ? ROLL_DURATION_TICKS : 0; + if (this.javaState != javaState) { + this.javaState = javaState; + javaEntityData.add(this.stateData()); + } + } + + @Override + public void tick() { + super.tick(); + if (this.rollingTicks > 0 && --this.rollingTicks == 0) { + this.javaState = SCARED; + this.sendEntityData(List.of(this.stateData())); + } + // The client needs the SCARED metadata before it can play the peek animation. + if (this.pendingPeek) { + this.pendingPeek = false; + PacketFactory.sendJavaEntityEvent(this.user, this, EntityEvent.ARMADILLO_PEEK); + } + } + + private EntityData stateData() { + return new EntityData(this.getJavaEntityDataIndex(EntityDataFields.ARMADILLO_STATE), VersionedTypes.V26_3.entityDataTypes().armadilloState, this.javaState); + } + +} diff --git a/src/main/java/net/raphimc/viabedrock/api/model/entity/Entity.java b/src/main/java/net/raphimc/viabedrock/api/model/entity/Entity.java index 5728257c1..b836f1f7e 100644 --- a/src/main/java/net/raphimc/viabedrock/api/model/entity/Entity.java +++ b/src/main/java/net/raphimc/viabedrock/api/model/entity/Entity.java @@ -97,6 +97,10 @@ public void remove() { public final void updateEntityData(final EntityData[] entityData) { final List javaEntityData = new ArrayList<>(); this.updateEntityData(entityData, javaEntityData); + this.sendEntityData(javaEntityData); + } + + public final void sendEntityData(final List javaEntityData) { final PacketWrapper setEntityData = PacketWrapper.create(ClientboundPackets26_3.SET_ENTITY_DATA, this.user); setEntityData.write(Types.VAR_INT, this.javaId); // entity id setEntityData.write(VersionedTypes.V26_3.entityDataList, javaEntityData); // entity data diff --git a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java index 39c8bf5fa..6ade7aafb 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java @@ -35,6 +35,7 @@ import com.viaversion.viaversion.protocols.v26_2to26_3.packet.ClientboundPackets26_3; import com.viaversion.viaversion.util.Key; import net.raphimc.viabedrock.ViaBedrock; +import net.raphimc.viabedrock.api.model.entity.ArmadilloEntity; import net.raphimc.viabedrock.api.model.entity.ClientPlayerEntity; import net.raphimc.viabedrock.api.model.entity.CustomEntity; import net.raphimc.viabedrock.api.model.entity.Entity; @@ -94,7 +95,7 @@ public static void register(final BedrockProtocol protocol) { attributes[i] = new EntityAttribute(name, currentValue, minValue, maxValue); } final EntityData[] entityData = wrapper.read(BedrockTypes.ENTITY_DATA_ARRAY); // entity data - wrapper.read(BedrockTypes.ENTITY_PROPERTIES); // entity properties + final EntityProperties entityProperties = wrapper.read(BedrockTypes.ENTITY_PROPERTIES); // entity properties wrapper.read(BedrockTypes.ENTITY_LINK_ARRAY); // entity links wrapper.read(BedrockTypes.OPTIONAL_PASSENGER_OF_BLOCKS); // passenger of blocks @@ -142,7 +143,14 @@ public static void register(final BedrockProtocol protocol) { if (entity instanceof LivingEntity livingEntity) { livingEntity.updateAttributes(attributes); } - entity.updateEntityData(entityData); + if (entity instanceof ArmadilloEntity) { + final List javaEntityData = new ArrayList<>(); + entity.updateEntityData(entityData, javaEntityData); + entity.translateEntityProperties(entityProperties, javaEntityData); + entity.sendEntityData(javaEntityData); + } else { + entity.updateEntityData(entityData); + } }); protocol.registerClientbound(ClientboundBedrockPackets.ADD_ITEM_ENTITY, ClientboundPackets26_3.ADD_ENTITY, wrapper -> { final EntityTracker entityTracker = wrapper.user().get(EntityTracker.class); diff --git a/src/main/java/net/raphimc/viabedrock/protocol/rewriter/EntityMetadataRewriter.java b/src/main/java/net/raphimc/viabedrock/protocol/rewriter/EntityMetadataRewriter.java index 1f7d7982c..e5392ede6 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/rewriter/EntityMetadataRewriter.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/rewriter/EntityMetadataRewriter.java @@ -27,6 +27,7 @@ import com.viaversion.viaversion.api.type.types.version.VersionedTypes; import com.viaversion.viaversion.util.Key; import net.raphimc.viabedrock.ViaBedrock; +import net.raphimc.viabedrock.api.model.entity.ArmadilloEntity; import net.raphimc.viabedrock.api.model.entity.Entity; import net.raphimc.viabedrock.api.model.entity.LivingEntity; import net.raphimc.viabedrock.api.model.entity.properties.EntityProperty; @@ -790,6 +791,10 @@ public static void rewriteProperties(final UserConnection user, final Entity ent ViaBedrock.getPlatform().getLogger().warning("Received int property " + integerProperty.identifier() + " for entity " + entity.type() + " with value " + value); } else if (property instanceof EntityProperty.EnumProperty enumProperty) { + if (i < 0 || i >= enumProperty.possibleValues().length) { + ViaBedrock.getPlatform().getLogger().warning("Received invalid enum value " + i + " for property " + enumProperty.identifier() + " on entity " + entity.type()); + continue; + } final String value = enumProperty.possibleValues()[i]; switch (enumProperty.identifier()) { @@ -871,6 +876,13 @@ public static void rewriteProperties(final UserConnection user, final Entity ent } } } + case "minecraft:armadillo_state" -> { + if (entity instanceof ArmadilloEntity armadillo) { + armadillo.translateState(value, javaEntityData); + } else { + ViaBedrock.getPlatform().getLogger().warning("Received armadillo_state for non-ARMADILLO entity " + entity.type()); + } + } case "minecraft:chest_interaction" -> { if (entity.javaType().is(EntityTypes26_3.COPPER_GOLEM)) { final int state = switch (value) { diff --git a/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityTracker.java b/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityTracker.java index 85d216c7e..428d10ed0 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityTracker.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityTracker.java @@ -59,7 +59,9 @@ public EntityTracker(final UserConnection user) { public Entity addEntity(final long uniqueId, final long runtimeId, final String type, final EntityTypes26_3 javaType) { final UUID javaUuid = UUID.randomUUID(); - if (javaType.isOrHasParent(EntityTypes26_3.ABSTRACT_HORSE)) { + if (javaType.is(EntityTypes26_3.ARMADILLO)) { + return this.addEntity(new ArmadilloEntity(this.user(), uniqueId, runtimeId, type, this.getNextJavaEntityId(), javaUuid)); + } else if (javaType.isOrHasParent(EntityTypes26_3.ABSTRACT_HORSE)) { return this.addEntity(new AbstractHorseEntity(this.user(), uniqueId, runtimeId, type, this.getNextJavaEntityId(), javaUuid, javaType)); } else if (javaType.isOrHasParent(EntityTypes26_3.MOB)) { return this.addEntity(new MobEntity(this.user(), uniqueId, runtimeId, type, this.getNextJavaEntityId(), javaUuid, javaType));