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 b0799450e..5728257c1 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 @@ -32,6 +32,7 @@ import net.raphimc.viabedrock.protocol.data.enums.bedrock.ActorFlags; import net.raphimc.viabedrock.protocol.data.enums.bedrock.generated.DataItemType; import net.raphimc.viabedrock.protocol.data.enums.java.generated.BossEventOperationType; +import net.raphimc.viabedrock.protocol.model.EntityProperties; import net.raphimc.viabedrock.protocol.model.Position3f; import net.raphimc.viabedrock.protocol.rewriter.EntityMetadataRewriter; import net.raphimc.viabedrock.protocol.types.BedrockTypes; @@ -123,6 +124,10 @@ public final void updateEntityData(final EntityData[] entityData, final List javaEntityData) { + EntityMetadataRewriter.rewriteProperties(this.user, this, properties, javaEntityData); + } + public void playSound(final String soundEvent) { final PacketWrapper levelSoundEvent = PacketWrapper.create(ClientboundBedrockPackets.LEVEL_SOUND_EVENT, this.user); levelSoundEvent.write(BedrockTypes.STRING, soundEvent); // event diff --git a/src/main/java/net/raphimc/viabedrock/api/model/entity/LivingEntity.java b/src/main/java/net/raphimc/viabedrock/api/model/entity/LivingEntity.java index 8f927e669..807f3ba12 100644 --- a/src/main/java/net/raphimc/viabedrock/api/model/entity/LivingEntity.java +++ b/src/main/java/net/raphimc/viabedrock/api/model/entity/LivingEntity.java @@ -158,11 +158,12 @@ public Map effects() { protected boolean translateAttribute(final EntityAttribute attribute, final PacketWrapper javaAttributes, final AtomicInteger attributeCount, final List javaEntityData) { return switch (attribute.name()) { - case "minecraft:attack_damage", "minecraft:knockback_resistance", "minecraft:movement" -> { + case "minecraft:attack_damage", "minecraft:knockback_resistance", "minecraft:movement", "minecraft:scale" -> { javaAttributes.write(Types.VAR_INT, BedrockProtocol.MAPPINGS.getJavaEntityAttributes().get(switch (attribute.name()) { case "minecraft:attack_damage" -> Attributes.ATTACK_DAMAGE; case "minecraft:knockback_resistance" -> Attributes.KNOCKBACK_RESISTANCE; case "minecraft:movement" -> Attributes.MOVEMENT_SPEED; + case "minecraft:scale" -> Attributes.SCALE; default -> throw new IllegalStateException("Unhandled entity attribute: " + attribute.name()); })); // attribute id javaAttributes.write(Types.DOUBLE, (double) attribute.computeClampedValue()); // base value diff --git a/src/main/java/net/raphimc/viabedrock/api/model/entity/properties/EntityProperty.java b/src/main/java/net/raphimc/viabedrock/api/model/entity/properties/EntityProperty.java new file mode 100644 index 000000000..c947ad171 --- /dev/null +++ b/src/main/java/net/raphimc/viabedrock/api/model/entity/properties/EntityProperty.java @@ -0,0 +1,30 @@ +/* + * This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock + * Copyright (C) 2023-2026 RK_01/RaphiMC 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 net.raphimc.viabedrock.api.model.entity.properties; + +public interface EntityProperty { + + record BooleanProperty(String identifier) implements EntityProperty { } + + record IntProperty(String identifier, int minValue, int maxValue) implements EntityProperty { } + + record FloatProperty(String identifier, float minValue, float maxValue) implements EntityProperty { } + + record EnumProperty(String identifier, String[] possibleValues) implements EntityProperty { } + +} diff --git a/src/main/java/net/raphimc/viabedrock/api/model/entity/properties/EntityPropertyList.java b/src/main/java/net/raphimc/viabedrock/api/model/entity/properties/EntityPropertyList.java new file mode 100644 index 000000000..fe80e1000 --- /dev/null +++ b/src/main/java/net/raphimc/viabedrock/api/model/entity/properties/EntityPropertyList.java @@ -0,0 +1,23 @@ +/* + * This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock + * Copyright (C) 2023-2026 RK_01/RaphiMC 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 net.raphimc.viabedrock.api.model.entity.properties; + +import java.util.List; + +public record EntityPropertyList(List intProperties, List floatProperties) { +} diff --git a/src/main/java/net/raphimc/viabedrock/protocol/BedrockProtocol.java b/src/main/java/net/raphimc/viabedrock/protocol/BedrockProtocol.java index 9595beabf..40d7f4398 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/BedrockProtocol.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/BedrockProtocol.java @@ -152,6 +152,7 @@ public void init(final UserConnection user) { user.put(new InventoryTransactionRewriter(user)); user.put(new MapTracker(user)); + user.put(new EntityPropertyTracker(user)); } @Override 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 a0d38d68d..46db70c4d 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/packet/EntityPackets.java @@ -19,6 +19,9 @@ import com.google.common.collect.Lists; import com.viaversion.nbt.tag.CompoundTag; +import com.viaversion.nbt.tag.ListTag; +import com.viaversion.nbt.tag.StringTag; +import com.viaversion.nbt.tag.Tag; import com.viaversion.viaversion.api.minecraft.Holder; import com.viaversion.viaversion.api.minecraft.PaintingVariant; import com.viaversion.viaversion.api.minecraft.Vector3d; @@ -36,6 +39,8 @@ import net.raphimc.viabedrock.api.model.entity.CustomEntity; 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; +import net.raphimc.viabedrock.api.model.entity.properties.EntityPropertyList; import net.raphimc.viabedrock.api.resourcepack.definition.EntityDefinitions; import net.raphimc.viabedrock.api.util.MathUtil; import net.raphimc.viabedrock.api.util.PacketFactory; @@ -50,11 +55,9 @@ import net.raphimc.viabedrock.protocol.data.enums.java.generated.EquipmentSlot; import net.raphimc.viabedrock.protocol.data.generated.java.EntityDataFields; import net.raphimc.viabedrock.protocol.data.generated.java.RegistryKeys; -import net.raphimc.viabedrock.protocol.model.BedrockItem; -import net.raphimc.viabedrock.protocol.model.EntityAttribute; -import net.raphimc.viabedrock.protocol.model.EntityEffect; -import net.raphimc.viabedrock.protocol.model.Position3f; +import net.raphimc.viabedrock.protocol.model.*; import net.raphimc.viabedrock.protocol.rewriter.ItemRewriter; +import net.raphimc.viabedrock.protocol.storage.EntityPropertyTracker; import net.raphimc.viabedrock.protocol.storage.EntityTracker; import net.raphimc.viabedrock.protocol.storage.GameSessionStorage; import net.raphimc.viabedrock.protocol.storage.ResourcePackStorage; @@ -504,7 +507,7 @@ public static void register(final BedrockProtocol protocol) { final long entityRuntimeId = wrapper.read(BedrockTypes.UNSIGNED_VAR_LONG); // entity runtime id 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.UNSIGNED_VAR_LONG); // tick final Entity entity = entityTracker.getEntityByRid(entityRuntimeId); @@ -515,9 +518,57 @@ public static void register(final BedrockProtocol protocol) { final List javaEntityData = new ArrayList<>(); entity.updateEntityData(entityData, javaEntityData); + entity.translateEntityProperties(entityProperties, javaEntityData); wrapper.write(Types.VAR_INT, entity.javaId()); // entity id wrapper.write(VersionedTypes.V26_3.entityDataList, javaEntityData); // entity data }); + protocol.registerClientbound(ClientboundBedrockPackets.SYNC_ENTITY_PROPERTY, null, wrapper -> { + wrapper.cancel(); + final Tag tag = wrapper.read(BedrockTypes.NETWORK_TAG); + + final EntityPropertyTracker epTracker = wrapper.user().get(EntityPropertyTracker.class); + + final String entityType = ((CompoundTag) tag).getString("type"); + final List intEntityProperties = new ArrayList<>(); + final List floatEntityProperties = new ArrayList<>(); + + final ListTag properties = ((CompoundTag) tag).getListTag("properties", CompoundTag.class); + for (CompoundTag property : properties) { + final EntityProperty newProperty = switch (property.getInt("type")) { + case 0 -> new EntityProperty.IntProperty( + property.getString("name"), + property.getInt("min"), + property.getInt("max") + ); + case 1 -> new EntityProperty.FloatProperty( + property.getString("name"), + property.getFloat("min"), + property.getFloat("max") + ); + case 2 -> new EntityProperty.BooleanProperty( + property.getString("name") + ); + case 3 -> new EntityProperty.EnumProperty( + property.getString("name"), + property.getListTag("enum", StringTag.class).stream().map(StringTag::getValue).toArray(String[]::new) + ); + default -> { + ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Received entity property with unknown type: " + property.getInt("type")); + yield null; + } + }; + + if (newProperty != null) { + if (newProperty instanceof EntityProperty.FloatProperty floatProperty) { + floatEntityProperties.add(floatProperty); + } else { + intEntityProperties.add(newProperty); + } + } + } + + epTracker.addEntity(entityType, new EntityPropertyList(intEntityProperties, floatEntityProperties)); + }); protocol.registerClientbound(ClientboundBedrockPackets.MOB_EFFECT, ClientboundPackets26_3.UPDATE_MOB_EFFECT, wrapper -> { final long entityRuntimeId = wrapper.read(BedrockTypes.UNSIGNED_VAR_LONG); // entity runtime id final MobEffectPacketPayload_Event event = MobEffectPacketPayload_Event.getByValue(wrapper.read(Types.BYTE), MobEffectPacketPayload_Event.Invalid); // event id 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 ed40425a3..1f7d7982c 100644 --- a/src/main/java/net/raphimc/viabedrock/protocol/rewriter/EntityMetadataRewriter.java +++ b/src/main/java/net/raphimc/viabedrock/protocol/rewriter/EntityMetadataRewriter.java @@ -17,19 +17,31 @@ */ package net.raphimc.viabedrock.protocol.rewriter; +import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.EulerAngle; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes26_3; import com.viaversion.viaversion.api.minecraft.entitydata.EntityData; +import com.viaversion.viaversion.api.minecraft.entitydata.EntityDataType; 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.Entity; +import net.raphimc.viabedrock.api.model.entity.LivingEntity; +import net.raphimc.viabedrock.api.model.entity.properties.EntityProperty; +import net.raphimc.viabedrock.api.model.entity.properties.EntityPropertyList; +import net.raphimc.viabedrock.protocol.BedrockProtocol; import net.raphimc.viabedrock.protocol.data.enums.bedrock.ActorDataIds; import net.raphimc.viabedrock.protocol.data.enums.bedrock.ActorFlags; import net.raphimc.viabedrock.protocol.data.generated.java.EntityDataFields; +import net.raphimc.viabedrock.protocol.model.EntityAttribute; +import net.raphimc.viabedrock.protocol.model.EntityProperties; +import net.raphimc.viabedrock.protocol.storage.EntityPropertyTracker; import net.raphimc.viabedrock.protocol.storage.EntityTracker; import net.raphimc.viabedrock.protocol.types.entitydata.EntityDataTypesBedrock; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Level; @@ -70,6 +82,19 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.SILENT), VersionedTypes.V26_3.entityDataTypes().booleanType, bedrockFlags.contains(ActorFlags.SILENT))); javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.NO_GRAVITY), VersionedTypes.V26_3.entityDataTypes().booleanType, !bedrockFlags.contains(ActorFlags.HAS_GRAVITY))); + if (entity.javaType().is(EntityTypes26_3.END_CRYSTAL)) { + final boolean showBottom = bedrockFlags.contains(ActorFlags.SHOW_BOTTOM); + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.SHOW_BOTTOM), VersionedTypes.V26_3.entityDataTypes().booleanType, showBottom)); + } + + if (entity.javaType().isOrHasParent(EntityTypes26_3.ABSTRACT_ARROW)) { + byte arrowBitMask = 0; + if (bedrockFlags.contains(ActorFlags.CRITICAL)) { + arrowBitMask |= 0x01; + } + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.FLAGS), VersionedTypes.V26_3.entityDataTypes().byteType, arrowBitMask)); + } + if (entity.javaType().isOrHasParent(EntityTypes26_3.MOB)) { byte mobBitMask = 0; if (bedrockFlags.contains(ActorFlags.NOAI)) { @@ -79,6 +104,16 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.MOB_FLAGS), VersionedTypes.V26_3.entityDataTypes().byteType, mobBitMask)); } + if (entity.javaType().is(EntityTypes26_3.GHAST)) { + final boolean attacking = bedrockFlags.contains(ActorFlags.CHARGED); + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.IS_CHARGING), VersionedTypes.V26_3.entityDataTypes().booleanType, attacking)); + } + + if (entity.javaType().is(EntityTypes26_3.SNOW_GOLEM)) { + final byte hasPumpkin = (byte) (bedrockFlags.contains(ActorFlags.SHEARED) ? 0 : 0x10); + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.FLAGS), VersionedTypes.V26_3.entityDataTypes().byteType, hasPumpkin)); + } + if (entity.javaType().is(EntityTypes26_3.ALLAY)) { final boolean dancing = bedrockFlags.contains(ActorFlags.DANCING); javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.DANCING), VersionedTypes.V26_3.entityDataTypes().booleanType, dancing)); @@ -120,11 +155,9 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi int sniffingState = 0; if (bedrockFlags.contains(ActorFlags.IDLING)) { sniffingState = 0; - } else if (false) { - //TODO: FEELING_HAPPY + } else if (bedrockFlags.contains(ActorFlags.DEPRECATED_3)) { // FEELING_HAPPY sniffingState = 1; - } else if (false) { - //TODO: SCENTING + } else if (bedrockFlags.contains(ActorFlags.DEPRECATED_1)) { // SCENTING sniffingState = 2; } else if (bedrockFlags.contains(ActorFlags.SNIFFING)) { sniffingState = 3; @@ -132,8 +165,7 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi sniffingState = 4; } else if (bedrockFlags.contains(ActorFlags.DIGGING)) { sniffingState = 5; - } else if (false) { - //TODO: RISING + } else if (bedrockFlags.contains(ActorFlags.DEPRECATED_2)) { // RISING sniffingState = 6; } else { sniffingState = 0; @@ -144,6 +176,11 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.STATE), VersionedTypes.V26_3.entityDataTypes().snifferState, sniffingState)); } + if (entity.javaType().is(EntityTypes26_3.STRIDER)) { + final boolean shaking = bedrockFlags.contains(ActorFlags.SHAKING); + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.SUFFOCATING), VersionedTypes.V26_3.entityDataTypes().booleanType, shaking)); + } + if (entity.javaType().is(EntityTypes26_3.TURTLE)) { //TODO: Test javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.HAS_EGG), VersionedTypes.V26_3.entityDataTypes().booleanType, bedrockFlags.contains(ActorFlags.IS_PREGNANT))); javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.LAYING_EGG), VersionedTypes.V26_3.entityDataTypes().booleanType, bedrockFlags.contains(ActorFlags.LAYING_EGG))); @@ -165,8 +202,13 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.FLAGS), VersionedTypes.V26_3.entityDataTypes().byteType, tamableBitMask)); } - if (entity.javaType().is(EntityTypes26_3.CAT)) { //TODO: Test - javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.IS_LYING), VersionedTypes.V26_3.entityDataTypes().booleanType, bedrockFlags.contains(ActorFlags.LAYING_DOWN))); + if (entity.javaType().is(EntityTypes26_3.CAT)) { + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.IS_LYING), VersionedTypes.V26_3.entityDataTypes().booleanType, bedrockFlags.contains(ActorFlags.RESTING))); + } + + if (entity.javaType().is(EntityTypes26_3.BLAZE)) { + final byte onFire = (byte) (bedrockFlags.contains(ActorFlags.CHARGED) ? 0x01 : 0); + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.FLAGS), VersionedTypes.V26_3.entityDataTypes().byteType, onFire)); } if (entity.javaType().is(EntityTypes26_3.BOGGED)) { @@ -182,6 +224,12 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.IS_IGNITED), VersionedTypes.V26_3.entityDataTypes().booleanType, ignited)); } + if (entity.javaType().is(EntityTypes26_3.SPIDER)) { + final boolean climbing = bedrockFlags.contains(ActorFlags.WALLCLIMBING); + final byte mask = (byte) (climbing ? 1 : 0); + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.FLAGS), VersionedTypes.V26_3.entityDataTypes().byteType, mask)); + } + if (entity.javaType().is(EntityTypes26_3.ZOGLIN)) { final boolean isBaby = bedrockFlags.contains(ActorFlags.BABY); javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.BABY), VersionedTypes.V26_3.entityDataTypes().booleanType, isBaby)); @@ -289,6 +337,10 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi final int javaVariant = variant; javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.VARIANT), VersionedTypes.V26_3.entityDataTypes().varIntType, javaVariant)); } + case CAT -> { // TODO: Test when I can + final int javaVariant = variant; + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.VARIANT), VersionedTypes.V26_3.entityDataTypes().varIntType, javaVariant)); + } default -> { if (variant != 0) { // For some reason bedrock seems to send variant 0 for many entities that don't have variants ViaBedrock.getPlatform().getLogger().warning("Received non-zero VARIANT " + variant + " for unsupported entity " + entity.type()); @@ -554,8 +606,8 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi } case DATA_WAITING -> { if (entity.javaType().is(EntityTypes26_3.AREA_EFFECT_CLOUD)) { - final boolean isWaiting = (boolean) entityData.getValue(); - javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.WAITING), VersionedTypes.V26_3.entityDataTypes().booleanType, isWaiting)); + final int isWaiting = readNumber(entityData).intValue(); + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.WAITING), VersionedTypes.V26_3.entityDataTypes().booleanType, isWaiting != 0)); } else { ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Received DATA_WAITING for non-AREA_EFFECT_CLOUD entity " + entity.type()); } @@ -567,6 +619,25 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Received DATA_PARTICLE for non-AREA_EFFECT_CLOUD entity " + entity.type()); } } + case VALUE -> { // TODO: Test + if (entity.javaType().is(EntityTypes26_3.EXPERIENCE_ORB)) { + final int exp = readNumber(entityData).intValue(); + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.VALUE), VersionedTypes.V26_3.entityDataTypes().varIntType, exp)); + } else { + ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Received VALUE for non-EXPERIENCE_ORB entity " + entity.type()); + } + } + case AUX_POWER -> { + if (entity.javaType().is(EntityTypes26_3.ARROW)) { + // Potion aux value used for an Arrow's trail. (Equal to the potion ID - 1) + //int potionId = readNumber(entityData).byteValue() + 1; + + // TODO: Find potion by id and get color + //javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.EFFECT_COLOR), VersionedTypes.V26_3.entityDataTypes().varIntType, to_do)); + } else { + ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Received AUX_POWER for non-ARROW entity " + entity.type()); + } + } case INV -> { if (entity.javaType().is(EntityTypes26_3.WITHER)) { final int invulnerabilityTicks = readNumber(entityData).intValue(); @@ -639,6 +710,28 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Received TARGET for non-GUARDIAN entity " + entity.type() + " with non-zero value " + targetId); } } + case BLOCK_TARGET -> { + if (entity.javaType().is(EntityTypes26_3.END_CRYSTAL)) { + final BlockPosition blockPosition = (BlockPosition) entityData.getValue(); // TODO: Test + + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.BEAM_TARGET), VersionedTypes.V26_3.entityDataTypes().optionalBlockPositionType, blockPosition)); + } else { + ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Received BLOCK_TARGET for non-END_CRYSTAL entity " + entity.type()); + } + } + case RESERVED_038 -> { // SCALE + final float scale = readNumber(entityData).floatValue(); + + // TODO: Armor stands might have special handling + + if (entity.javaType().isOrHasParent(EntityTypes26_3.LIVING_ENTITY) && scale != 0f) { + final LivingEntity livingEntity = (LivingEntity) entity; + + livingEntity.updateAttributes(new EntityAttribute[]{new EntityAttribute("minecraft:scale", scale, (float) 1 / 16, 16)}); + } + } + case RESERVED_053 -> { } // BOUNDING_BOX_WIDTH + case RESERVED_054 -> { } // BOUNDING_BOX_HEIGHT case AGENT, BALLOON_ANCHOR -> { } // Education edition only, ignore default -> { @@ -649,6 +742,188 @@ public static boolean rewrite(final UserConnection user, final Entity entity, fi return true; } + public static void rewriteProperties(final UserConnection user, final Entity entity, final EntityProperties properties, final List javaEntityData) { + final EntityPropertyTracker epTracker = user.get(EntityPropertyTracker.class); + final CompoundTag registries = BedrockProtocol.MAPPINGS.getJavaRegistries(); + final EntityPropertyList expectedProperties = epTracker.getEntityProperties(entity.type()); + if (expectedProperties == null) { + //ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Failed to find expected properties for entity " + entity.type()); + return; + } + + final Iterator intIterator = expectedProperties.intProperties().iterator(); + + for (int i : properties.intProperties().values()) { + final EntityProperty property = intIterator.next(); + + if (property instanceof EntityProperty.BooleanProperty booleanProperty) { + final boolean value = i != 0; + + switch (booleanProperty.identifier()) { + case "minecraft:can_move" -> { + if (entity.javaType().is(EntityTypes26_3.HAPPY_GHAST)) { + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.STAYS_STILL), VersionedTypes.V26_3.entityDataTypes().booleanType, !value)); + } else { + ViaBedrock.getPlatform().getLogger().warning("Received can_move property for non-HAPPY_GHAST entity " + entity.type()); + } + } + case "minecraft:has_nectar" -> { + if (entity.javaType().is(EntityTypes26_3.BEE)) { + final byte bitmask = value ? (byte) 0x08 : 0; // TODO + + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.FLAGS), VersionedTypes.V26_3.entityDataTypes().byteType, bitmask)); + } else { + ViaBedrock.getPlatform().getLogger().warning("Received has_nectar property for non-BEE entity " + entity.type()); + } + } + case "minecraft:has_flower" -> { + if (entity.javaType().is(EntityTypes26_3.COPPER_GOLEM)) { + // TODO + } else { + ViaBedrock.getPlatform().getLogger().warning("Received has_flower property for non-COPPER_GOLEM entity " + entity.type()); + } + } + default -> ViaBedrock.getPlatform().getLogger().warning("Received boolean property " + booleanProperty.identifier() + " for entity " + entity.type() + " with value " + value); + } + } else if (property instanceof EntityProperty.IntProperty integerProperty) { + final int value = i; + + ViaBedrock.getPlatform().getLogger().warning("Received int property " + integerProperty.identifier() + " for entity " + entity.type() + " with value " + value); + } else if (property instanceof EntityProperty.EnumProperty enumProperty) { + final String value = enumProperty.possibleValues()[i]; + + switch (enumProperty.identifier()) { + case "minecraft:climate_variant" -> { + final String namespacedVariant = Key.namespaced(value); + + final String registryName = switch (entity.javaType()) { + case CHICKEN -> "minecraft:chicken_variant"; + case COW -> "minecraft:cow_variant"; + case PIG -> "minecraft:pig_variant"; + default -> { + ViaBedrock.getPlatform().getLogger().warning("Received climate_variant for unsupported entity " + entity.type()); + yield null; + } + }; + final EntityDataType variantType = switch (entity.javaType()) { + case CHICKEN -> VersionedTypes.V26_3.entityDataTypes().chickenVariantType; + case COW -> VersionedTypes.V26_3.entityDataTypes().cowVariantType; + case PIG -> VersionedTypes.V26_3.entityDataTypes().pigVariantType; + default -> null; + }; + + if (registryName != null && variantType != null) { + final CompoundTag variantRegistry = registries.getCompoundTag(registryName); + // TODO: Could be cleaned up + int javaVariant = -1; + for (String key : variantRegistry.keySet()) { + javaVariant++; + if (key.equals(namespacedVariant)) { + break; + } + } + + if (javaVariant != -1) { + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.VARIANT), variantType, javaVariant)); + } else { + ViaBedrock.getPlatform().getLogger().warning("Failed to find variant " + namespacedVariant + " in registry " + registryName + " for entity " + entity.type()); + } + } + } + case "minecraft:sound_variant" -> { + final String namespacedVariant = Key.namespaced(value); + + final String registryName = switch (entity.javaType()) { + case CHICKEN -> "minecraft:chicken_sound_variant"; + case COW -> "minecraft:cow_sound_variant"; + case PIG -> "minecraft:pig_sound_variant"; + case WOLF -> "minecraft:wolf_sound_variant"; + case CAT -> "minecraft:cat_sound_variant"; + default -> { + ViaBedrock.getPlatform().getLogger().warning("Received sound_variant for unsupported entity " + entity.type()); + yield null; + } + }; + final EntityDataType variantType = switch (entity.javaType()) { + case CHICKEN -> VersionedTypes.V26_3.entityDataTypes().chickenSoundVariant; + case COW -> VersionedTypes.V26_3.entityDataTypes().cowSoundVariant; + case PIG -> VersionedTypes.V26_3.entityDataTypes().pigSoundVariant; + case WOLF -> VersionedTypes.V26_3.entityDataTypes().wolfSoundVariantType; + case CAT -> VersionedTypes.V26_3.entityDataTypes().catSoundVariant; + default -> null; + }; + + if (registryName != null && variantType != null) { + final CompoundTag variantRegistry = registries.getCompoundTag(registryName); + // TODO: Could be cleaned up + int javaVariant = -1; + for (String key : variantRegistry.keySet()) { + javaVariant++; + if (key.equals(namespacedVariant)) { + break; + } + } + + if (javaVariant != -1) { + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.SOUND_VARIANT), variantType, javaVariant)); + } else { + ViaBedrock.getPlatform().getLogger().warning("Failed to find sound variant " + namespacedVariant + " in registry " + registryName + " for entity " + entity.type()); + } + } + } + case "minecraft:chest_interaction" -> { + if (entity.javaType().is(EntityTypes26_3.COPPER_GOLEM)) { + final int state = switch (value) { + case "none" -> 0; // IDLE + case "take" -> 1; // GETTING_ITEM + case "take_fail" -> 2; // GETTING_NO_ITEM + case "put" -> 3; // DROPPING_ITEM + case "put_fail" -> 4; // DROPPING_NO_ITEM + default -> -1; + }; + + if (state != -1) { + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.COPPER_GOLEM_STATE), VersionedTypes.V26_3.entityDataTypes().copperGolemState, state)); + } else { + ViaBedrock.getPlatform().getLogger().warning("Received unknown chest_interaction state " + value + " for COPPER_GOLEM entity " + entity.type()); + } + } else { + ViaBedrock.getPlatform().getLogger().warning("Received chest_interaction for non-COPPER_GOLEM entity " + entity.type()); + } + } + case "minecraft:oxidation_level" -> { + if (entity.javaType().is(EntityTypes26_3.COPPER_GOLEM)) { + final int state = switch (value) { + case "unoxidized" -> 0; // UNAFFECTED + case "exposed" -> 1; // EXPOSED + case "weathered" -> 2; // WEATHERED + case "oxidized" -> 3; // OXIDIZED + default -> -1; + }; + + if (state != -1) { + javaEntityData.add(new EntityData(entity.getJavaEntityDataIndex(EntityDataFields.WEATHER_STATE), VersionedTypes.V26_3.entityDataTypes().weatheringCopperState, state)); + } else { + ViaBedrock.getPlatform().getLogger().warning("Received unknown oxidation_level state " + value + " for COPPER_GOLEM entity " + entity.type()); + } + } else { + ViaBedrock.getPlatform().getLogger().warning("Received oxidation_level for non-COPPER_GOLEM entity " + entity.type()); + } + } + default -> ViaBedrock.getPlatform().getLogger().warning("Received enum property " + enumProperty.identifier() + " for entity " + entity.type() + " with value " + value); + } + } + } + + final Iterator floatIterator = expectedProperties.floatProperties().iterator(); + + for (float value : properties.floatProperties().values()) { + final EntityProperty.FloatProperty property = floatIterator.next(); + + ViaBedrock.getPlatform().getLogger().warning("Received float property " + property.identifier() + " for entity " + entity.type() + " with value " + value); + } + } + private static Number readNumber(final EntityData data) { if (data.dataType() == null || data.getValue() == null) { throw new IllegalArgumentException("EntityData " + data.id() + " has null data type or value"); diff --git a/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityPropertyTracker.java b/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityPropertyTracker.java new file mode 100644 index 000000000..24904cb4e --- /dev/null +++ b/src/main/java/net/raphimc/viabedrock/protocol/storage/EntityPropertyTracker.java @@ -0,0 +1,41 @@ +/* + * This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock + * Copyright (C) 2023-2026 RK_01/RaphiMC 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 net.raphimc.viabedrock.protocol.storage; + +import com.viaversion.viaversion.api.connection.StoredObject; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.libs.fastutil.objects.Object2ObjectOpenHashMap; +import net.raphimc.viabedrock.api.model.entity.properties.EntityPropertyList; + +public class EntityPropertyTracker extends StoredObject { + + Object2ObjectOpenHashMap entities = new Object2ObjectOpenHashMap<>(); + + public EntityPropertyTracker(final UserConnection user) { + super(user); + } + + public void addEntity(final String entityId, final EntityPropertyList properties) { + this.entities.put(entityId, properties); + } + + public EntityPropertyList getEntityProperties(final String entityId) { + return this.entities.get(entityId); + } + +}