diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java
index eda8e0c84..73814628f 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java
@@ -184,7 +184,7 @@ public final class EntityDefinitions {
                 .addTranslator(MetadataType.INT, Entity::setAir) // Air/bubbles
                 .addTranslator(MetadataType.OPTIONAL_CHAT, Entity::setDisplayName)
                 .addTranslator(MetadataType.BOOLEAN, Entity::setDisplayNameVisible)
-                .addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.SILENT, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
+                .addTranslator(MetadataType.BOOLEAN, Entity::setSilent)
                 .addTranslator(MetadataType.BOOLEAN, Entity::setGravity)
                 .addTranslator(MetadataType.POSE, (entity, entityMetadata) -> entity.setPose(entityMetadata.getValue()))
                 .addTranslator(MetadataType.INT, Entity::setFreezing)
diff --git a/core/src/main/java/org/geysermc/geyser/entity/ChestBoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ChestBoatEntity.java
similarity index 96%
rename from core/src/main/java/org/geysermc/geyser/entity/ChestBoatEntity.java
rename to core/src/main/java/org/geysermc/geyser/entity/type/ChestBoatEntity.java
index 76e98d953..724bf921e 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/ChestBoatEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/ChestBoatEntity.java
@@ -23,11 +23,11 @@
  * @link https://github.com/GeyserMC/Geyser
  */
 
-package org.geysermc.geyser.entity;
+package org.geysermc.geyser.entity.type;
 
 import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
 import com.nukkitx.math.vector.Vector3f;
-import org.geysermc.geyser.entity.type.BoatEntity;
+import org.geysermc.geyser.entity.EntityDefinition;
 import org.geysermc.geyser.session.GeyserSession;
 import org.geysermc.geyser.util.InteractionResult;
 import org.geysermc.geyser.util.InteractiveTag;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java
index 52efcf67e..144d1cbf9 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java
@@ -94,6 +94,8 @@ public class Entity {
     private float boundingBoxWidth;
     @Setter(AccessLevel.NONE)
     protected String nametag = "";
+    @Setter(AccessLevel.NONE)
+    protected boolean silent = false;
     /* Metadata end */
 
     protected List<Entity> passengers = Collections.emptyList();
@@ -148,6 +150,12 @@ public class Entity {
         setFlag(EntityFlag.HAS_COLLISION, true);
         setFlag(EntityFlag.CAN_SHOW_NAME, true);
         setFlag(EntityFlag.CAN_CLIMB, true);
+        // Let the Java server (or us) supply all sounds for an entity
+        setClientSideSilent();
+    }
+
+    protected void setClientSideSilent() {
+        setFlag(EntityFlag.SILENT, true);
     }
 
     public void spawnEntity() {
@@ -370,6 +378,10 @@ public class Entity {
         dirtyMetadata.put(EntityData.NAMETAG_ALWAYS_SHOW, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0));
     }
 
+    public final void setSilent(BooleanEntityMetadata entityMetadata) {
+        silent = entityMetadata.getPrimitiveValue();
+    }
+
     public void setGravity(BooleanEntityMetadata entityMetadata) {
         setFlag(EntityFlag.HAS_GRAVITY, !entityMetadata.getPrimitiveValue());
     }
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/EvokerFangsEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/EvokerFangsEntity.java
index 03c71cec6..af7dca68c 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/EvokerFangsEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/EvokerFangsEntity.java
@@ -27,7 +27,6 @@ package org.geysermc.geyser.entity.type;
 
 import com.nukkitx.math.vector.Vector3f;
 import com.nukkitx.protocol.bedrock.data.entity.EntityData;
-import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
 import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
 import org.geysermc.geyser.entity.EntityDefinition;
 import org.geysermc.geyser.session.GeyserSession;
@@ -58,7 +57,7 @@ public class EvokerFangsEntity extends Entity implements Tickable {
 
     public void setAttackStarted() {
         this.attackStarted = true;
-        if (!getFlag(EntityFlag.SILENT)) {
+        if (!silent) {
             // Play the chomp sound
             PlaySoundPacket packet = new PlaySoundPacket();
             packet.setPosition(this.position);
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java
index 57b597781..75bdd9021 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java
@@ -28,17 +28,16 @@ package org.geysermc.geyser.entity.type;
 import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
 import com.nukkitx.math.vector.Vector3f;
 import com.nukkitx.protocol.bedrock.data.entity.EntityData;
-import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
 import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
 import lombok.Getter;
 import org.geysermc.geyser.entity.EntityDefinitions;
 import org.geysermc.geyser.entity.type.player.PlayerEntity;
-import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.level.physics.BoundingBox;
-import org.geysermc.geyser.translator.collision.BlockCollision;
-import org.geysermc.geyser.level.block.BlockStateValues;
-import org.geysermc.geyser.registry.BlockRegistries;
 import org.geysermc.geyser.level.block.BlockPositionIterator;
+import org.geysermc.geyser.level.block.BlockStateValues;
+import org.geysermc.geyser.level.physics.BoundingBox;
+import org.geysermc.geyser.registry.BlockRegistries;
+import org.geysermc.geyser.session.GeyserSession;
+import org.geysermc.geyser.translator.collision.BlockCollision;
 import org.geysermc.geyser.util.BlockUtils;
 
 import java.util.UUID;
@@ -129,7 +128,7 @@ public class FishingHookEntity extends ThrowableEntity {
     }
 
     private void sendSplashSound(GeyserSession session) {
-        if (!getFlag(EntityFlag.SILENT)) {
+        if (!silent) {
             float volume = (float) (0.2f * Math.sqrt(0.2 * (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) + motion.getY() * motion.getY()));
             if (volume > 1) {
                 volume = 1;
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java
index 1d689e806..6adcb4694 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java
@@ -213,7 +213,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
      */
     private void effectTick() {
         Random random = ThreadLocalRandom.current();
-        if (!getFlag(EntityFlag.SILENT)) {
+        if (!silent) {
             if (Math.cos(wingPosition * 2f * Math.PI) <= -0.3f && Math.cos(lastWingPosition * 2f * Math.PI) >= -0.3f) {
                 PlaySoundPacket playSoundPacket = new PlaySoundPacket();
                 playSoundPacket.setSound("mob.enderdragon.flap");
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java
index 3a8e9d351..0ec12da83 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/WardenEntity.java
@@ -31,13 +31,19 @@ import com.nukkitx.math.GenericMath;
 import com.nukkitx.math.vector.Vector3f;
 import com.nukkitx.protocol.bedrock.data.entity.EntityData;
 import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
+import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
 import org.geysermc.geyser.entity.EntityDefinition;
+import org.geysermc.geyser.entity.type.Tickable;
 import org.geysermc.geyser.session.GeyserSession;
 import org.geysermc.geyser.util.MathUtils;
 
 import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class WardenEntity extends MonsterEntity implements Tickable {
+    private int heartBeatDelay;
+    private int tickCount;
 
-public class WardenEntity extends MonsterEntity {
     public WardenEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
         super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
     }
@@ -53,6 +59,23 @@ public class WardenEntity extends MonsterEntity {
 
     public void setAngerLevel(IntEntityMetadata entityMetadata) {
         float anger = (float) entityMetadata.getPrimitiveValue() / 80f;
-        dirtyMetadata.put(EntityData.HEARTBEAT_INTERVAL_TICKS, 40 - GenericMath.floor(MathUtils.clamp(anger, 0.0F, 1.0F) * 30F));
+        heartBeatDelay = 40 - GenericMath.floor(MathUtils.clamp(anger, 0.0F, 1.0F) * 30F);
+        dirtyMetadata.put(EntityData.HEARTBEAT_INTERVAL_TICKS, heartBeatDelay);
+    }
+
+    @Override
+    public void tick() {
+        if (++tickCount % heartBeatDelay == 0 && !silent) {
+            // We have to do these calculations because they're clientside on Java Edition but we mute entities
+            // to prevent hearing their step sounds
+            ThreadLocalRandom random = ThreadLocalRandom.current();
+
+            PlaySoundPacket packet = new PlaySoundPacket();
+            packet.setSound("mob.warden.heartbeat");
+            packet.setPosition(position);
+            packet.setPitch(1.0f);
+            packet.setVolume((random.nextFloat() - random.nextFloat()) * 0.2f + 1.0f);
+            session.sendUpstreamPacket(packet);
+        }
     }
 }
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
index db39a34db..d4b703c40 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java
@@ -75,6 +75,11 @@ public class SessionPlayerEntity extends PlayerEntity {
         valid = true;
     }
 
+    @Override
+    protected void setClientSideSilent() {
+        // Do nothing, since we want the session player to hear their own footstep sounds for example.
+    }
+
     @Override
     public void spawnEntity() {
         // Already logged in
diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/SoundMapping.java b/core/src/main/java/org/geysermc/geyser/registry/type/SoundMapping.java
index 4120b6eb5..27b5e631d 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/type/SoundMapping.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/type/SoundMapping.java
@@ -38,10 +38,10 @@ public class SoundMapping {
 
     public SoundMapping(String java, String bedrock, String playsound, int extraData, String identifier, boolean levelEvent) {
         this.java = java;
-        this.bedrock = bedrock == null || bedrock.equalsIgnoreCase("") ? null : bedrock;
-        this.playsound = playsound == null || playsound.equalsIgnoreCase("") ? null : playsound;
+        this.bedrock = bedrock == null || bedrock.isEmpty() ? null : bedrock;
+        this.playsound = playsound == null || playsound.isEmpty() ? null : playsound;
         this.extraData = extraData;
-        this.identifier = identifier == null || identifier.equalsIgnoreCase("") ? ":" : identifier;
+        this.identifier = identifier == null || identifier.isEmpty() ? ":" : identifier;
         this.levelEvent = levelEvent;
     }
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java
index de4a2c22b..ee342ce3f 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaEntityEventTranslator.java
@@ -26,7 +26,6 @@
 package org.geysermc.geyser.translator.protocol.java.entity;
 
 import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundEntityEventPacket;
-import com.nukkitx.math.vector.Vector3f;
 import com.nukkitx.protocol.bedrock.data.LevelEventType;
 import com.nukkitx.protocol.bedrock.data.SoundEvent;
 import com.nukkitx.protocol.bedrock.data.entity.EntityData;
@@ -42,7 +41,6 @@ import org.geysermc.geyser.session.GeyserSession;
 import org.geysermc.geyser.translator.protocol.PacketTranslator;
 import org.geysermc.geyser.translator.protocol.Translator;
 
-import java.util.Random;
 import java.util.concurrent.ThreadLocalRandom;
 
 @Translator(packet = ClientboundEntityEventPacket.class)
@@ -50,6 +48,7 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
 
     @Override
     public void translate(GeyserSession session, ClientboundEntityEventPacket packet) {
+        System.out.println(packet);
         Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
         if (entity == null)
             return;
@@ -180,8 +179,9 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
             case IRON_GOLEM_EMPTY_HAND:
                 entityEventPacket.setType(EntityEventType.GOLEM_FLOWER_WITHDRAW);
                 break;
-            case IRON_GOLEM_ATTACK:
-                if (entity.getDefinition() == EntityDefinitions.IRON_GOLEM || entity.getDefinition() == EntityDefinitions.EVOKER_FANGS) {
+            case ATTACK:
+                if (entity.getDefinition() == EntityDefinitions.IRON_GOLEM || entity.getDefinition() == EntityDefinitions.EVOKER_FANGS
+                        || entity.getDefinition() == EntityDefinitions.WARDEN) {
                     entityEventPacket.setType(EntityEventType.ATTACK_START);
                     if (entity.getDefinition() == EntityDefinitions.EVOKER_FANGS) {
                         ((EvokerFangsEntity) entity).setAttackStarted();
@@ -236,28 +236,14 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
                 break;
             case MAKE_POOF_PARTICLES:
                 if (entity instanceof LivingEntity) {
-                    // Not ideal, but...
-                    // LevelEventType.PARTICLE_DEATH_SMOKE doesn't work (as of 1.18.2 Bedrock)
-                    // EntityEventType.DEATH_SMOKE_CLOUD also plays the entity death noise
-                    // Bedrock sends the particles through EntityEventType.DEATH, but Java despawns the entity
-                    // prematurely so they don't show up.
-                    Vector3f position = entity.getPosition();
-                    float baseX = position.getX();
-                    float baseY = position.getY();
-                    float baseZ = position.getZ();
-                    float height = entity.getBoundingBoxHeight();
-                    float width = entity.getBoundingBoxWidth();
-                    Random random = ThreadLocalRandom.current();
-                    for (int i = 0; i < 20; i++) {
-                        // Reconstruct the Java Edition (1.18.1) logic, but in floats
-                        float x = baseX + width * (2.0f * random.nextFloat() - 1f);
-                        float y = baseY + height * random.nextFloat();
-                        float z = baseZ + width * (2.0f * random.nextFloat() - 1f);
-                        LevelEventPacket levelEventPacket = new LevelEventPacket();
-                        levelEventPacket.setPosition(Vector3f.from(x, y, z));
-                        levelEventPacket.setType(LevelEventType.PARTICLE_EXPLODE);
-                        session.sendUpstreamPacket(levelEventPacket);
-                    }
+                    // Note that this event usually makes noise, but because we set all entities as silent on the
+                    // client end this isn't an issue.
+                    entityEventPacket.setType(EntityEventType.DEATH_SMOKE_CLOUD);
+                }
+                break;
+            case WARDEN_RECEIVE_SIGNAL:
+                if (entity.getDefinition() == EntityDefinitions.WARDEN) {
+                    entityEventPacket.setType(EntityEventType.VIBRATION_DETECTED);
                 }
                 break;
         }
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSoundEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSoundEntityTranslator.java
index 11a047805..06f141aa6 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSoundEntityTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaSoundEntityTranslator.java
@@ -40,6 +40,6 @@ public class JavaSoundEntityTranslator extends PacketTranslator<ClientboundSound
         if (entity == null) {
             return;
         }
-        SoundUtils.playBuiltinSound(session, packet.getSound(), entity.getPosition(), packet.getPitch());
+        SoundUtils.playBuiltinSound(session, packet.getSound(), entity.getPosition(), packet.getVolume(), packet.getPitch());
     }
 }
diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSoundTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSoundTranslator.java
index 626c4527c..5b83ff551 100644
--- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSoundTranslator.java
+++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaSoundTranslator.java
@@ -38,6 +38,6 @@ public class JavaSoundTranslator extends PacketTranslator<ClientboundSoundPacket
     @Override
     public void translate(GeyserSession session, ClientboundSoundPacket packet) {
         Vector3f position = Vector3f.from(packet.getX(), packet.getY(), packet.getZ());
-        SoundUtils.playBuiltinSound(session, packet.getSound(), position, packet.getPitch());
+        SoundUtils.playBuiltinSound(session, packet.getSound(), position, packet.getVolume(), packet.getPitch());
     }
 }
diff --git a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java
index 44ec06244..6540c17ca 100644
--- a/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java
+++ b/core/src/main/java/org/geysermc/geyser/util/SoundUtils.java
@@ -33,6 +33,7 @@ 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.PlaySoundPacket;
 import org.geysermc.geyser.GeyserImpl;
 import org.geysermc.geyser.level.block.BlockStateValues;
 import org.geysermc.geyser.registry.BlockRegistries;
@@ -74,10 +75,9 @@ public class SoundUtils {
             return "";
         }
 
-        // Drop the namespace
-        int colonPos = packetSound.indexOf(":");
-        if (colonPos != -1) {
-            packetSound = packetSound.substring(colonPos + 1);
+        // Drop the Minecraft namespace if applicable
+        if (packetSound.startsWith("minecraft:")) {
+            packetSound = packetSound.substring("minecraft:".length());
         }
 
         SoundMapping soundMapping = Registries.SOUNDS.get(packetSound);
@@ -97,7 +97,7 @@ public class SoundUtils {
      * @param position the position
      * @param pitch the pitch
      */
-    public static void playBuiltinSound(GeyserSession session, BuiltinSound javaSound, Vector3f position, float pitch) {
+    public static void playBuiltinSound(GeyserSession session, BuiltinSound javaSound, Vector3f position, float volume, float pitch) {
         String packetSound = javaSound.getName();
 
         SoundMapping soundMapping = Registries.SOUNDS.get(packetSound);
@@ -106,6 +106,17 @@ public class SoundUtils {
             return;
         }
 
+        if (soundMapping.getPlaysound() != null) {
+            // We always prefer the PlaySound mapping because we can control volume and pitch
+            PlaySoundPacket playSoundPacket = new PlaySoundPacket();
+            playSoundPacket.setSound(soundMapping.getPlaysound());
+            playSoundPacket.setPosition(position);
+            playSoundPacket.setVolume(volume);
+            playSoundPacket.setPitch(pitch);
+            session.sendUpstreamPacket(playSoundPacket);
+            return;
+        }
+
         if (soundMapping.isLevelEvent()) {
             LevelEventPacket levelEventPacket = new LevelEventPacket();
             levelEventPacket.setPosition(position);