diff --git a/core/pom.xml b/core/pom.xml
index 2d06c6bff..038f9ec8d 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -72,7 +72,7 @@
com.nukkitx
nbt
- 2.1.0
+ 2.2.1
compile
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java
index 195835a15..8fcfa381f 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelEventTranslator.java
@@ -25,15 +25,15 @@
package org.geysermc.geyser.translator.protocol.java.level;
+import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
import com.github.steveice10.mc.protocol.data.game.level.event.*;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
+import com.nukkitx.nbt.NbtMap;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
-import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
-import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
-import com.nukkitx.protocol.bedrock.packet.TextPacket;
+import com.nukkitx.protocol.bedrock.packet.*;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.session.GeyserSession;
@@ -44,6 +44,7 @@ import org.geysermc.geyser.translator.protocol.Translator;
import java.util.Collections;
import java.util.Locale;
+import java.util.Set;
@Translator(packet = ClientboundLevelEventPacket.class)
public class JavaLevelEventTranslator extends PacketTranslator {
@@ -257,6 +258,56 @@ public class JavaLevelEventTranslator extends PacketTranslator effectPacket.setType(LevelEventType.PARTICLE_WAX_ON);
case WAX_OFF -> effectPacket.setType(LevelEventType.PARTICLE_WAX_OFF);
case SCRAPE -> effectPacket.setType(LevelEventType.PARTICLE_SCRAPE);
+ case SCULK_BLOCK_CHARGE -> {
+ SculkBlockChargeEventData eventData = (SculkBlockChargeEventData) packet.getData();
+ LevelEventGenericPacket levelEventPacket = new LevelEventGenericPacket();
+ // TODO add SCULK_BLOCK_CHARGE sound
+ if (eventData.getCharge() > 0) {
+ levelEventPacket.setEventId(2037);
+ levelEventPacket.setTag(
+ NbtMap.builder()
+ .putInt("x", packet.getPosition().getX())
+ .putInt("y", packet.getPosition().getY())
+ .putInt("z", packet.getPosition().getZ())
+ .putShort("charge", (short) eventData.getCharge())
+ .putShort("facing", encodeFacing(eventData.getBlockFaces())) // TODO check if this is actually correct
+ .build()
+ );
+ } else {
+ levelEventPacket.setEventId(2038);
+ levelEventPacket.setTag(
+ NbtMap.builder()
+ .putInt("x", packet.getPosition().getX())
+ .putInt("y", packet.getPosition().getY())
+ .putInt("z", packet.getPosition().getZ())
+ .build()
+ );
+ }
+ session.sendUpstreamPacket(levelEventPacket);
+ return;
+ }
+ case SCULK_SHRIEKER_SHRIEK -> {
+ LevelEventGenericPacket levelEventPacket = new LevelEventGenericPacket();
+ levelEventPacket.setEventId(2035);
+ levelEventPacket.setTag(
+ NbtMap.builder()
+ .putInt("originX", packet.getPosition().getX())
+ .putInt("originY", packet.getPosition().getY())
+ .putInt("originZ", packet.getPosition().getZ())
+ .build()
+ );
+ session.sendUpstreamPacket(levelEventPacket);
+
+ LevelSoundEventPacket soundEventPacket = new LevelSoundEventPacket();
+ soundEventPacket.setSound(SoundEvent.SCULK_SHRIEKER_SHRIEK);
+ soundEventPacket.setPosition(packet.getPosition().toFloat());
+ soundEventPacket.setExtraData(-1);
+ soundEventPacket.setIdentifier("");
+ soundEventPacket.setBabySound(false);
+ soundEventPacket.setRelativeVolumeDisabled(false);
+ session.sendUpstreamPacket(soundEventPacket);
+ return;
+ }
default -> {
GeyserImpl.getInstance().getLogger().debug("Unhandled level event: " + packet.getEvent());
return;
@@ -264,4 +315,27 @@ public class JavaLevelEventTranslator extends PacketTranslator blockFaces) {
+ short facing = 0;
+ if (blockFaces.contains(Direction.DOWN)) {
+ facing |= 1;
+ }
+ if (blockFaces.contains(Direction.UP)) {
+ facing |= 1 << 1;
+ }
+ if (blockFaces.contains(Direction.SOUTH)) {
+ facing |= 1 << 2;
+ }
+ if (blockFaces.contains(Direction.WEST)) {
+ facing |= 1 << 3;
+ }
+ if (blockFaces.contains(Direction.NORTH)) {
+ facing |= 1 << 4;
+ }
+ if (blockFaces.contains(Direction.EAST)) {
+ facing |= 1 << 5;
+ }
+ return facing;
+ }
}
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java
index de586b600..a413421a3 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaLevelParticlesTranslator.java
@@ -27,13 +27,18 @@ package org.geysermc.geyser.translator.protocol.java.level;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.level.particle.*;
+import com.github.steveice10.mc.protocol.data.game.level.particle.positionsource.BlockPositionSource;
+import com.github.steveice10.mc.protocol.data.game.level.particle.positionsource.EntityPositionSource;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelParticlesPacket;
import com.nukkitx.math.vector.Vector3f;
+import com.nukkitx.nbt.NbtMap;
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.LevelEventGenericPacket;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.SpawnParticleEffectPacket;
+import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@@ -131,6 +136,39 @@ public class JavaLevelParticlesTranslator extends PacketTranslator {
+ VibrationParticleData data = (VibrationParticleData) particle.getData();
+
+ Vector3f target;
+ if (data.getPositionSource() instanceof BlockPositionSource blockPositionSource) {
+ target = blockPositionSource.getPosition().toFloat().add(0.5f, 0.5f, 0.5f);
+ } else if (data.getPositionSource() instanceof EntityPositionSource entityPositionSource) {
+ Entity entity = session.getEntityCache().getEntityByJavaId(entityPositionSource.getEntityId());
+ if (entity != null) {
+ target = entity.getPosition().up(entityPositionSource.getYOffset());
+ } else {
+ session.getGeyser().getLogger().debug("Unable to find entity with Java Id: " + entityPositionSource.getEntityId() + " for vibration particle.");
+ return null;
+ }
+ } else {
+ session.getGeyser().getLogger().debug("Unknown position source " + data.getPositionSource() + " for vibration particle.");
+ return null;
+ }
+
+ return (position) -> {
+ LevelEventGenericPacket packet = new LevelEventGenericPacket();
+ packet.setEventId(2027);
+ packet.setTag(
+ NbtMap.builder()
+ .putCompound("origin", buildVec3PositionTag(position))
+ .putCompound("target", buildVec3PositionTag(target)) // There is a way to target an entity but that takes an attachPos instead of a y offset
+ .putFloat("speed", 20f)
+ .putFloat("timeToLive", data.getArrivalTicks() / 20f)
+ .build()
+ );
+ return packet;
+ };
+ }
default -> {
ParticleMapping particleMapping = Registries.PARTICLES.get(particle.getType());
if (particleMapping == null) { //TODO ensure no particle can be null
@@ -160,4 +198,13 @@ public class JavaLevelParticlesTranslator extends PacketTranslator