Interact with an optional resource pack to add more features (#2176)

See https://github.com/GeyserMC/Geyser/wiki/GeyserOptionalPack
This commit is contained in:
Camotoy 2021-05-10 00:48:01 -04:00 committed by GitHub
parent 0691bb67b4
commit 95bcd4000f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 330 additions and 73 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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;

View file

@ -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);

View file

@ -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.
*/

View file

@ -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<AttributeData> attributes = new ArrayList<>();
for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> 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);
}
}

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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<ServerEntityAnimationPacket> {
@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<ServerEntity
case SWING_ARM:
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
break;
case EAT_FOOD: // ACTUALLY SWING OFF HAND
// Use the OptionalPack to trigger the animation
AnimateEntityPacket offHandPacket = new AnimateEntityPacket();
offHandPacket.setAnimation("animation.player.attack.rotations.offhand");
offHandPacket.setNextState("default");
offHandPacket.setBlendOutTime(0.0f);
offHandPacket.setStopExpression("query.any_animation_finished");
offHandPacket.setController("__runtime_controller");
offHandPacket.getRuntimeEntityIds().add(entity.getGeyserId());
session.sendUpstreamPacket(offHandPacket);
return;
case CRITICAL_HIT:
animatePacket.setAction(AnimatePacket.Action.CRITICAL_HIT);
break;
case ENCHANTMENT_CRITICAL_HIT:
animatePacket.setAction(AnimatePacket.Action.MAGIC_CRITICAL_HIT);
animatePacket.setAction(AnimatePacket.Action.MAGIC_CRITICAL_HIT); // Unsure if this does anything
// Spawn custom particle
SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket();
stringPacket.setIdentifier("geyseropt:enchanted_hit_multiple");
stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
stringPacket.setPosition(Vector3f.ZERO);
stringPacket.setUniqueEntityId(entity.getGeyserId());
session.sendUpstreamPacket(stringPacket);
break;
case LEAVE_BED:
animatePacket.setAction(AnimatePacket.Action.WAKE_UP);

View file

@ -26,12 +26,10 @@
package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.world.particle.BlockParticleData;
import com.github.steveice10.mc.protocol.data.game.world.particle.DustParticleData;
import com.github.steveice10.mc.protocol.data.game.world.particle.FallingDustParticleData;
import com.github.steveice10.mc.protocol.data.game.world.particle.ItemParticleData;
import com.github.steveice10.mc.protocol.data.game.world.particle.*;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerSpawnParticlePacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.BedrockPacket;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
@ -43,65 +41,117 @@ import org.geysermc.connector.network.translators.effect.EffectRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.DimensionUtils;
import java.util.Random;
import java.util.function.Function;
@Translator(packet = ServerSpawnParticlePacket.class)
public class JavaSpawnParticleTranslator extends PacketTranslator<ServerSpawnParticlePacket> {
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<Vector3f, BedrockPacket> 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<Vector3f, BedrockPacket> 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;
}
}
}
}
}

@ -1 +1 @@
Subproject commit c846b8200eb8ebb37207666f7eddb83f2b636c37
Subproject commit 53e13b7a0d2ea14df71ed0c9582d29a9b4fb4453