From 95bcd4000f852cff44a5b3247f57615f6a197d8c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 10 May 2021 00:48:01 -0400 Subject: [PATCH] Interact with an optional resource pack to add more features (#2176) See https://github.com/GeyserMC/Geyser/wiki/GeyserOptionalPack --- README.md | 3 +- .../connector/entity/AbstractArrowEntity.java | 3 + .../connector/entity/attribute/Attribute.java | 2 + .../entity/attribute/AttributeType.java | 10 +- .../entity/living/ArmorStandEntity.java | 78 +++++++++ .../entity/living/IronGolemEntity.java | 80 +++++++++ .../entity/living/animal/RabbitEntity.java | 5 +- .../entity/living/monster/ShulkerEntity.java | 3 + .../raid/SpellcasterIllagerEntity.java | 2 + .../connector/entity/player/PlayerEntity.java | 11 ++ .../connector/entity/type/EntityType.java | 8 +- .../entity/JavaEntityAnimationTranslator.java | 34 +++- .../world/JavaSpawnParticleTranslator.java | 162 ++++++++++++------ connector/src/main/resources/mappings | 2 +- 14 files changed, 330 insertions(+), 73 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java diff --git a/README.md b/README.md index bc267f06f..a51c61f9f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ The following things cannot be fixed without changes to Bedrock. As of now, they - Custom heads in inventories - Clickable links in chat - Glowing effect -- Custom armor stand poses + +Do note that some things require the [GeyserOptionalPack](https://github.com/GeyserMC/Geyser/wiki/GeyserOptionalPack) in order to function, such as custom armor stand poses. ## Compiling 1. Clone the repo to your computer diff --git a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java index 70dbdf959..25e8d37a1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java @@ -36,6 +36,9 @@ public class AbstractArrowEntity extends Entity { public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // Set the correct texture if using the resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, entityType == EntityType.SPECTRAL_ARROW); + setMotion(motion); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java index 2a2d47ba9..9cb803898 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/Attribute.java @@ -28,10 +28,12 @@ package org.geysermc.connector.entity.attribute; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import lombok.ToString; @Getter @Setter @AllArgsConstructor +@ToString public class Attribute { private AttributeType type; diff --git a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java index ccd0bcb5b..6877bb7c6 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/attribute/AttributeType.java @@ -57,12 +57,12 @@ public enum AttributeType { HUNGER(null, "minecraft:player.hunger", 0f, 20f, 20f), SATURATION(null, "minecraft:player.saturation", 0f, 20f, 20f); - private String javaIdentifier; - private String bedrockIdentifier; + private final String javaIdentifier; + private final String bedrockIdentifier; - private float minimum; - private float maximum; - private float defaultValue; + private final float minimum; + private final float maximum; + private final float defaultValue; public Attribute getAttribute(float value) { return getAttribute(value, maximum); diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 3d1005510..13f8a4c1d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -27,6 +27,7 @@ package org.geysermc.connector.entity.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Rotation; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; @@ -157,6 +158,72 @@ public class ArmorStandEntity extends LivingEntity { updateSecondEntityStatus(false); } + + // The following values don't do anything on normal Bedrock. + // But if given a resource pack, then we can use these values to control armor stand visual properties + metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x04) != 0x04); // Has arms + metadata.getFlags().setFlag(EntityFlag.ADMIRING, (xd & 0x08) == 0x08); // Has no baseplate + } else { + EntityData dataLeech = null; + EntityFlag negativeXToggle = null; + EntityFlag negativeYToggle = null; + EntityFlag negativeZToggle = null; + switch (entityMetadata.getId()) { + case 15: // Head + dataLeech = EntityData.MARK_VARIANT; + negativeXToggle = EntityFlag.INTERESTED; + negativeYToggle = EntityFlag.CHARGED; + negativeZToggle = EntityFlag.POWERED; + break; + case 16: // Body + dataLeech = EntityData.VARIANT; + negativeXToggle = EntityFlag.IN_LOVE; + negativeYToggle = EntityFlag.CELEBRATING; + negativeZToggle = EntityFlag.CELEBRATING_SPECIAL; + break; + case 17: // Left arm + dataLeech = EntityData.TRADE_TIER; + negativeXToggle = EntityFlag.CHARGING; + negativeYToggle = EntityFlag.CRITICAL; + negativeZToggle = EntityFlag.DANCING; + break; + case 18: // Right arm + dataLeech = EntityData.MAX_TRADE_TIER; + negativeXToggle = EntityFlag.ELDER; + negativeYToggle = EntityFlag.EMOTING; + negativeZToggle = EntityFlag.IDLING; + break; + case 19: // Left leg + dataLeech = EntityData.SKIN_ID; + negativeXToggle = EntityFlag.IS_ILLAGER_CAPTAIN; + negativeYToggle = EntityFlag.IS_IN_UI; + negativeZToggle = EntityFlag.LINGERING; + break; + case 20: // Right leg + dataLeech = EntityData.HURT_DIRECTION; + negativeXToggle = EntityFlag.IS_PREGNANT; + negativeYToggle = EntityFlag.SHEARED; + negativeZToggle = EntityFlag.STALKING; + break; + } + if (dataLeech != null) { + // Indicate that rotation should be checked + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); + + Rotation rotation = (Rotation) entityMetadata.getValue(); + int rotationX = getRotation(rotation.getPitch()); + int rotationY = getRotation(rotation.getYaw()); + int rotationZ = getRotation(rotation.getRoll()); + // The top bit acts like binary and determines if each rotation goes above 100 + // We don't do this for the negative values out of concerns of the number being too big + int topBit = (Math.abs(rotationX) >= 100 ? 4 : 0) + (Math.abs(rotationY) >= 100 ? 2 : 0) + (Math.abs(rotationZ) >= 100 ? 1 : 0); + int value = (topBit * 1000000) + ((Math.abs(rotationX) % 100) * 10000) + ((Math.abs(rotationY) % 100) * 100) + (Math.abs(rotationZ) % 100); + metadata.put(dataLeech, value); + // Set the entity flags if a value is negative + metadata.getFlags().setFlag(negativeXToggle, rotationX < 0); + metadata.getFlags().setFlag(negativeYToggle, rotationY < 0); + metadata.getFlags().setFlag(negativeZToggle, rotationZ < 0); + } } if (secondEntity != null) { secondEntity.updateBedrockMetadata(entityMetadata, session); @@ -302,6 +369,17 @@ public class ArmorStandEntity extends LivingEntity { } } + private int getRotation(float rotation) { + rotation = rotation % 360f; + if (rotation < -180f) { + rotation += 360f; + } else if (rotation >= 180f) { + // 181 -> -179 + rotation = -(180 - (rotation - 180)); + } + return (int) rotation; + } + /** * If this armor stand is not a marker, set its bounding box size and scale. */ diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java new file mode 100644 index 000000000..5fca355f5 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/entity/living/IronGolemEntity.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.entity.living; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.AttributeData; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import org.geysermc.connector.entity.attribute.AttributeType; +import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.AttributeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class IronGolemEntity extends GolemEntity { + + public IronGolemEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { + super(entityId, geyserId, entityType, position, motion, rotation); + // Indicate that we should show cracks through a resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); + // Required, or else the overlay is black + metadata.put(EntityData.COLOR_2, (byte) 0); + } + + @Override + public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); + if (entityMetadata.getId() == 8) { + // Required so the resource pack sees the entity health + attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(metadata.getFloat(EntityData.HEALTH), 100f)); + updateBedrockAttributes(session); + } + } + + @Override + public void updateBedrockAttributes(GeyserSession session) { + if (!valid) return; + + List attributes = new ArrayList<>(); + for (Map.Entry entry : this.attributes.entrySet()) { + if (!entry.getValue().getType().isBedrockAttribute()) + continue; + + attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue())); + } + + UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket(); + updateAttributesPacket.setRuntimeEntityId(geyserId); + updateAttributesPacket.setAttributes(attributes); + session.sendUpstreamPacket(updateAttributesPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index 9137cd23a..9a4691cc0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -53,9 +53,12 @@ public class RabbitEntity extends AnimalEntity { int variant = (int) entityMetadata.getValue(); // Change the killer bunny to display as white since it only exists on Java Edition - if (variant == 99) { + boolean isKillerBunny = variant == 99; + if (isKillerBunny) { variant = 1; } + // Allow the resource pack to adjust to the killer bunny + metadata.getFlags().setFlag(EntityFlag.BRIBED, isKillerBunny); metadata.put(EntityData.VARIANT, variant); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java index f31dde69c..b99f66ac8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/ShulkerEntity.java @@ -31,6 +31,7 @@ import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.living.GolemEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -39,6 +40,8 @@ public class ShulkerEntity extends GolemEntity { public ShulkerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // Indicate that invisibility should be fixed through the resource pack + metadata.getFlags().setFlag(EntityFlag.BRIBED, true); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java index d43bd8fb7..6d4500a1c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/raid/SpellcasterIllagerEntity.java @@ -39,6 +39,8 @@ public class SpellcasterIllagerEntity extends AbstractIllagerEntity { public SpellcasterIllagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + // OptionalPack usage + metadata.getFlags().setFlag(EntityFlag.BRIBED, this.entityType == EntityType.ILLUSIONER); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index b8be69ab3..f8eeef307 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -82,6 +82,9 @@ public class PlayerEntity extends LivingEntity { profile = gameProfile; uuid = gameProfile.getId(); username = gameProfile.getName(); + + // For the OptionalPack, set all bits as invisible by default as this matches Java Edition behavior + metadata.put(EntityData.MARK_VARIANT, 0xff); } @Override @@ -280,6 +283,14 @@ public class PlayerEntity extends LivingEntity { session.sendUpstreamPacket(attributesPacket); } + if (entityMetadata.getId() == 16) { + // OptionalPack usage for toggling skin bits + // In Java Edition, a bit being set means that part should be enabled + // However, to ensure that the pack still works on other servers, we invert the bit so all values by default + // are true (0). + metadata.put(EntityData.MARK_VARIANT, ~((byte) entityMetadata.getValue()) & 0xff); + } + // Parrot occupying shoulder if (entityMetadata.getId() == 18 || entityMetadata.getId() == 19) { CompoundTag tag = (CompoundTag) entityMetadata.getValue(); diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index 43658f63b..cf169a209 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -55,7 +55,7 @@ public enum EntityType { SQUID(SquidEntity.class, 17, 0.8f), RABBIT(RabbitEntity.class, 18, 0.5f, 0.4f), BAT(BatEntity.class, 19, 0.9f, 0.5f), - IRON_GOLEM(GolemEntity.class, 20, 2.7f, 1.4f), + IRON_GOLEM(IronGolemEntity.class, 20, 2.7f, 1.4f), SNOW_GOLEM(SnowGolemEntity.class, 21, 1.9f, 0.7f), OCELOT(OcelotEntity.class, 22, 0.35f, 0.3f), HORSE(HorseEntity.class, 23, 1.6f, 1.3965f), @@ -147,7 +147,7 @@ public enum EntityType { EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), VEX(VexEntity.class, 105, 0.8f, 0.4f), ICE_BOMB(Entity.class, 106, 0f), - BALLOON(Entity.class, 107, 0f), //TODO + BALLOON(Entity.class, 107, 0f), PUFFERFISH(PufferFishEntity.class, 108, 0.7f, 0.7f), SALMON(AbstractFishEntity.class, 109, 0.5f, 0.7f), DROWNED(ZombieEntity.class, 110, 1.95f, 0.6f), @@ -168,9 +168,9 @@ public enum EntityType { ITEM_FRAME(ItemFrameEntity.class, 0, 0, 0), /** - * Not an entity in Bedrock, so we replace it with a Pillager + * Not an entity in Bedrock, so we replace it with an evoker */ - ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"), + ILLUSIONER(SpellcasterIllagerEntity.class, 104, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:evocation_illager"), /** * Not an entity in Bedrock, but used for the Ender Dragon's multiple hitboxes diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java index 735a5ea47..255c4f466 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java @@ -25,22 +25,27 @@ package org.geysermc.connector.network.translators.java.entity; +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityAnimationPacket; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.packet.AnimateEntityPacket; +import com.nukkitx.protocol.bedrock.packet.AnimatePacket; +import com.nukkitx.protocol.bedrock.packet.SpawnParticleEffectPacket; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityAnimationPacket; -import com.nukkitx.protocol.bedrock.packet.AnimatePacket; +import org.geysermc.connector.utils.DimensionUtils; @Translator(packet = ServerEntityAnimationPacket.class) public class JavaEntityAnimationTranslator extends PacketTranslator { @Override public void translate(ServerEntityAnimationPacket packet, GeyserSession session) { - Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + Entity entity; if (packet.getEntityId() == session.getPlayerEntity().getEntityId()) { entity = session.getPlayerEntity(); + } else { + entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); } if (entity == null) return; @@ -51,11 +56,30 @@ public class JavaEntityAnimationTranslator extends PacketTranslator { + private final Random random = new Random(); @Override public void translate(ServerSpawnParticlePacket packet, GeyserSession session) { - LevelEventPacket particle = new LevelEventPacket(); - switch (packet.getParticle().getType()) { - case BLOCK: - particle.setType(LevelEventType.PARTICLE_DESTROY_BLOCK_NO_SOUND); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - particle.setData(session.getBlockTranslator().getBedrockBlockId(((BlockParticleData) packet.getParticle().getData()).getBlockState())); - session.sendUpstreamPacket(particle); - break; - case FALLING_DUST: - //In fact, FallingDustParticle should have data like DustParticle, - //but in MCProtocol, its data is BlockState(1). - particle.setType(LevelEventType.PARTICLE_FALLING_DUST); - particle.setData(session.getBlockTranslator().getBedrockBlockId(((FallingDustParticleData)packet.getParticle().getData()).getBlockState())); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - case ITEM: - ItemStack javaItem = ((ItemParticleData)packet.getParticle().getData()).getItemStack(); - ItemData bedrockItem = ItemTranslator.translateToBedrock(session, javaItem); - int id = bedrockItem.getId(); - int damage = bedrockItem.getDamage(); - particle.setType(LevelEventType.PARTICLE_ITEM_BREAK); - particle.setData(id << 16 | damage); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - case DUST: - DustParticleData data = (DustParticleData)packet.getParticle().getData(); - int r = (int) (data.getRed()*255); - int g = (int) (data.getGreen()*255); - int b = (int) (data.getBlue()*255); - particle.setType(LevelEventType.PARTICLE_FALLING_DUST); - particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - break; - default: - LevelEventType typeParticle = EffectRegistry.getParticleLevelEventType(packet.getParticle().getType()); - if (typeParticle != null) { - particle.setType(typeParticle); - particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(particle); - } else { - String stringParticle = EffectRegistry.getParticleString(packet.getParticle().getType()); - if (stringParticle != null) { - SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); - stringPacket.setIdentifier(stringParticle); - stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension())); - stringPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); - session.sendUpstreamPacket(stringPacket); - } + Function particleCreateFunction = createParticle(session, packet.getParticle()); + if (particleCreateFunction != null) { + if (packet.getAmount() == 0) { + // 0 means don't apply the offset + Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ()); + session.sendUpstreamPacket(particleCreateFunction.apply(position)); + } else { + for (int i = 0; i < packet.getAmount(); i++) { + double offsetX = this.random.nextGaussian() * (double) packet.getOffsetX(); + double offsetY = this.random.nextGaussian() * (double) packet.getOffsetY(); + double offsetZ = this.random.nextGaussian() * (double) packet.getOffsetZ(); + Vector3f position = Vector3f.from(packet.getX() + offsetX, packet.getY() + offsetY, packet.getZ() + offsetZ); + + session.sendUpstreamPacket(particleCreateFunction.apply(position)); } - break; + } + } else { + // Null is only returned when no particle of this type is found + session.getConnector().getLogger().debug("Unhandled particle packet: " + packet); } } + /** + * @param session the Bedrock client session. + * @param particle the Java particle to translate to a Bedrock equivalent. + * @return a function to create a packet with a specified particle, in the event we need to spawn multiple particles + * with different offsets. + */ + private Function createParticle(GeyserSession session, Particle particle) { + switch (particle.getType()) { + case BLOCK: { + int blockState = session.getBlockTranslator().getBedrockBlockId(((BlockParticleData) particle.getData()).getBlockState()); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_CRACK_BLOCK); + packet.setPosition(position); + packet.setData(blockState); + return packet; + }; + } + case FALLING_DUST: { + int blockState = session.getBlockTranslator().getBedrockBlockId(((FallingDustParticleData) particle.getData()).getBlockState()); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + // In fact, FallingDustParticle should have data like DustParticle, + // but in MCProtocol, its data is BlockState(1). + packet.setType(LevelEventType.PARTICLE_FALLING_DUST); + packet.setData(blockState); + packet.setPosition(position); + return packet; + }; + } + case ITEM: { + ItemStack javaItem = ((ItemParticleData) particle.getData()).getItemStack(); + ItemData bedrockItem = ItemTranslator.translateToBedrock(session, javaItem); + int data = bedrockItem.getId() << 16 | bedrockItem.getDamage(); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_ITEM_BREAK); + packet.setData(data); + packet.setPosition(position); + return packet; + }; + } + case DUST: { + DustParticleData data = (DustParticleData) particle.getData(); + int r = (int) (data.getRed() * 255); + int g = (int) (data.getGreen() * 255); + int b = (int) (data.getBlue() * 255); + int rgbData = ((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff); + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(LevelEventType.PARTICLE_FALLING_DUST); + packet.setData(rgbData); + packet.setPosition(position); + return packet; + }; + } + default: + LevelEventType typeParticle = EffectRegistry.getParticleLevelEventType(particle.getType()); + if (typeParticle != null) { + return (position) -> { + LevelEventPacket packet = new LevelEventPacket(); + packet.setType(typeParticle); + packet.setPosition(position); + return packet; + }; + } else { + String stringParticle = EffectRegistry.getParticleString(particle.getType()); + if (stringParticle != null) { + int dimensionId = DimensionUtils.javaToBedrock(session.getDimension()); + return (position) -> { + SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); + stringPacket.setIdentifier(stringParticle); + stringPacket.setDimensionId(dimensionId); + stringPacket.setPosition(position); + return stringPacket; + }; + } else { + return null; + } + } + } + } } \ No newline at end of file diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index c846b8200..53e13b7a0 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit c846b8200eb8ebb37207666f7eddb83f2b636c37 +Subproject commit 53e13b7a0d2ea14df71ed0c9582d29a9b4fb4453