From 4b5f07b37cbf29149c4188b8ae78625215e112df Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:28:19 -0700 Subject: [PATCH] Get sound working --- .../patchbukkit/bridge/NativePatchBukkit.java | 74 +++++++++++++++++-- .../patchbukkit/entity/PatchBukkitPlayer.java | 14 ++-- .../registry/PatchBukkitSound.java | 6 ++ rust/src/java/native_callbacks/mod.rs | 7 +- rust/src/java/native_callbacks/sound.rs | 48 +++++++++++- rust/src/java/resources.rs | 2 +- 6 files changed, 132 insertions(+), 19 deletions(-) diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java index 9c89f11..fee9c66 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java @@ -19,7 +19,8 @@ public class NativePatchBukkit { private static MethodHandle getWorldNative; private static MethodHandle freeStringNative; private static MethodHandle getRegistryDataNative; - private static MethodHandle entityPlaySoundNative; + private static MethodHandle playerEntityPlaySoundNative; + private static MethodHandle playerPlaySoundNative; // Struct layout matching Rust's #[repr(C)] AbilitiesFFI private static final StructLayout ABILITIES_LAYOUT = @@ -188,7 +189,8 @@ public class NativePatchBukkit { long freeStringAddr, long getWorldAddr, long getRegistryDataAddr, - long entityPlaySoundAddr + long playerEntityPlaySoundAddr, + long playerPlaySoundAddr ) { // void rust_send_message(const char* uuid, const char* message) sendMessageNative = LINKER.downcallHandle( @@ -250,10 +252,10 @@ public class NativePatchBukkit { FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS) ); - // void rust_entity_play_sound(const char* player_uuid, const char* sound_name, + // void rust_player_entity_play_sound(const char* player_uuid, const char* sound_name, // const char* sound_category, const char* entity_uuid, float volume, float pitch) - entityPlaySoundNative = LINKER.downcallHandle( - MemorySegment.ofAddress(entityPlaySoundAddr), + playerEntityPlaySoundNative = LINKER.downcallHandle( + MemorySegment.ofAddress(playerEntityPlaySoundAddr), FunctionDescriptor.ofVoid( ValueLayout.ADDRESS, // player_uuid ValueLayout.ADDRESS, // sound_name @@ -263,6 +265,22 @@ public class NativePatchBukkit { ValueLayout.JAVA_FLOAT // pitch ) ); + + // void rust_player_play_sound(const char* player_uuid, const char* sound_name, + // const char* sound_category, double x, double y, double z, float volume, float pitch) + playerPlaySoundNative = LINKER.downcallHandle( + MemorySegment.ofAddress(playerPlaySoundAddr), + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, // player_uuid + ValueLayout.ADDRESS, // sound_name + ValueLayout.ADDRESS, // sound_category + ValueLayout.JAVA_DOUBLE, // x + ValueLayout.JAVA_DOUBLE, // y + ValueLayout.JAVA_DOUBLE, // z + ValueLayout.JAVA_FLOAT, // volume + ValueLayout.JAVA_FLOAT // pitch + ) + ); } /** @@ -457,7 +475,7 @@ public class NativePatchBukkit { * @param volume Volume (1.0 = normal) * @param pitch Pitch (1.0 = normal) */ - public static void entityPlaySound( + public static void playerEntityPlaySound( UUID playerUuid, String soundName, String soundCategory, @@ -471,7 +489,7 @@ public class NativePatchBukkit { MemorySegment soundCategoryStr = arena.allocateFrom(soundCategory); MemorySegment entityUuidStr = arena.allocateFrom(entityUuid.toString()); - entityPlaySoundNative.invokeExact( + playerEntityPlaySoundNative.invokeExact( playerUuidStr, soundNameStr, soundCategoryStr, @@ -483,4 +501,46 @@ public class NativePatchBukkit { throw new RuntimeException("Failed to call native entityPlaySound", t); } } + + /** + * Play a sound effect at a location, heard by a specific player. + * + * @param playerUuid The player who will hear the sound + * @param soundName Sound identifier (e.g. "block.note_block.chime") + * @param soundCategory Category name (e.g. "master", "music", "blocks") + * @param x X coordinate + * @param y Y coordinate + * @param z Z coordinate + * @param volume Volume (1.0 = normal) + * @param pitch Pitch (1.0 = normal) + */ + public static void playerPlaySound( + UUID playerUuid, + String soundName, + String soundCategory, + double x, + double y, + double z, + float volume, + float pitch + ) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment playerUuidStr = arena.allocateFrom(playerUuid.toString()); + MemorySegment soundNameStr = arena.allocateFrom(soundName); + MemorySegment soundCategoryStr = arena.allocateFrom(soundCategory); + + playerPlaySoundNative.invokeExact( + playerUuidStr, + soundNameStr, + soundCategoryStr, + x, + y, + z, + volume, + pitch + ); + } catch (Throwable t) { + throw new RuntimeException("Failed to call native playerPlaySound", t); + } + } } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java index 903fe44..234bb1c 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java @@ -62,6 +62,7 @@ import org.jetbrains.annotations.Unmodifiable; import org.jetbrains.annotations.UnmodifiableView; import org.patchbukkit.PatchBukkitServer; import org.patchbukkit.bridge.NativePatchBukkit; +import org.patchbukkit.registry.PatchBukkitSound; import com.destroystokyo.paper.ClientOption; import com.destroystokyo.paper.Title; @@ -685,14 +686,13 @@ public class PatchBukkitPlayer @Override public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'playSound'"); + var patchBukkitSound = (PatchBukkitSound) sound; + NativePatchBukkit.playerPlaySound(this.uuid, patchBukkitSound.getOriginalName(), category.name(), location.x(), location.y(), location.z(), volume, pitch); } @Override public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'playSound'"); + NativePatchBukkit.playerPlaySound(this.uuid, sound, category.name(), location.x(), location.y(), location.z(), volume, pitch); } @Override @@ -711,13 +711,13 @@ public class PatchBukkitPlayer @Override public void playSound(Entity entity, Sound sound, SoundCategory category, float volume, float pitch) { - NativePatchBukkit.entityPlaySound(this.uuid, sound.toString(), category.name(), entity.getUniqueId(), volume, pitch); + var patchBukkitSound = (PatchBukkitSound) sound; + NativePatchBukkit.playerEntityPlaySound(this.uuid, patchBukkitSound.getOriginalName(), category.name(), entity.getUniqueId(), volume, pitch); } @Override public void playSound(Entity entity, String sound, SoundCategory category, float volume, float pitch) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'playSound'"); + NativePatchBukkit.playerEntityPlaySound(this.uuid, sound, category.name(), entity.getUniqueId(), volume, pitch); } @Override diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitSound.java b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitSound.java index 5acd1f3..45dd950 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitSound.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/registry/PatchBukkitSound.java @@ -9,15 +9,21 @@ public class PatchBukkitSound implements Sound { private final NamespacedKey namespacedKey; private final Key adventureKey; private final String enumName; + private final String originalName; private final int protocolId; public PatchBukkitSound(String soundName, int protocolId) { this.namespacedKey = new NamespacedKey(NamespacedKey.MINECRAFT, soundName); this.adventureKey = Key.key(Key.MINECRAFT_NAMESPACE, soundName); this.protocolId = protocolId; + this.originalName = soundName; this.enumName = soundName.toUpperCase().replace('.', '_'); } + public String getOriginalName() { + return originalName; + } + @Override public NamespacedKey getKey() { return namespacedKey; diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index a221217..70e3592 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -45,7 +45,9 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> { let free_string_addr = memory::rust_free_string as *const () as i64; let get_world_addr = world::rust_get_world as *const () as i64; let rust_get_registry_data_addr = registry::rust_get_registry_data as *const () as i64; - let rust_entity_play_sound_addr = sound::rust_entity_play_sound as *const () as i64; + let rust_player_entity_play_sound_addr = + sound::rust_player_entity_play_sound as *const () as i64; + let rust_player_play_sound_addr = sound::rust_player_play_sound as *const () as i64; jvm.invoke_static( "org.patchbukkit.bridge.NativePatchBukkit", @@ -59,7 +61,8 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> { InvocationArg::try_from(free_string_addr)?.into_primitive()?, InvocationArg::try_from(get_world_addr)?.into_primitive()?, InvocationArg::try_from(rust_get_registry_data_addr)?.into_primitive()?, - InvocationArg::try_from(rust_entity_play_sound_addr)?.into_primitive()?, + InvocationArg::try_from(rust_player_entity_play_sound_addr)?.into_primitive()?, + InvocationArg::try_from(rust_player_play_sound_addr)?.into_primitive()?, ], )?; diff --git a/rust/src/java/native_callbacks/sound.rs b/rust/src/java/native_callbacks/sound.rs index dcad2cf..7b22112 100644 --- a/rust/src/java/native_callbacks/sound.rs +++ b/rust/src/java/native_callbacks/sound.rs @@ -1,13 +1,14 @@ -use std::ffi::{c_char, c_float}; +use std::ffi::{c_char, c_double, c_float}; use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector}; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_protocol::{IdOr, java::client::play::CEntitySoundEffect}; +use pumpkin_util::math::vector3::Vector3; use rand::{RngExt, rng}; use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; -pub extern "C" fn rust_entity_play_sound( +pub extern "C" fn rust_player_entity_play_sound( player_uuid_ptr: *const c_char, sound_name_ptr: *const c_char, sound_category_ptr: *const c_char, @@ -65,3 +66,46 @@ pub extern "C" fn rust_entity_play_sound( }); } } + +pub extern "C" fn rust_player_play_sound( + player_uuid_ptr: *const c_char, + sound_name_ptr: *const c_char, + sound_category_ptr: *const c_char, + x: c_double, + y: c_double, + z: c_double, + volume: c_float, + pitch: c_float, +) { + let player_uuid_str = get_string(player_uuid_ptr); + let sound_name = get_string(sound_name_ptr); + let sound_category = get_string(sound_category_ptr); + + if let Some(ctx) = CALLBACK_CONTEXT.get() { + let player_uuid = uuid::Uuid::parse_str(&player_uuid_str).unwrap(); + + ctx.runtime.spawn(async move { + let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid); + + if let Some(player) = player { + let sound = match Sound::from_name(&sound_name) { + Some(sound) => sound, + None => return, + }; + + let category = match SoundCategory::from_name(&sound_category.to_lowercase()) { + Some(category) => category, + None => return, + }; + + let seed = rng().random::(); + + let position: Vector3 = Vector3::new(x, y, z); + + player + .play_sound(sound as u16, category, &position, volume, pitch, seed) + .await; + } + }); + } +} diff --git a/rust/src/java/resources.rs b/rust/src/java/resources.rs index 44fa0d7..5a4b3bb 100644 --- a/rust/src/java/resources.rs +++ b/rust/src/java/resources.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::{Context, Result}; +use anyhow::Result; use rust_embed::Embed; #[derive(Embed)]