From 185822a5fe03357bf68ed5c5f9fa9f68926985cf Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Sun, 1 Feb 2026 11:04:32 -0700 Subject: [PATCH] Add entity play sound --- .../patchbukkit/bridge/NativePatchBukkit.java | 55 ++++++++- .../patchbukkit/entity/PatchBukkitPlayer.java | 3 +- rust/Cargo.lock | 115 +++++------------- rust/Cargo.toml | 5 +- rust/src/java/native_callbacks/message.rs | 1 - rust/src/java/native_callbacks/mod.rs | 3 + rust/src/java/native_callbacks/registry.rs | 1 - rust/src/java/native_callbacks/sound.rs | 67 ++++++++++ 8 files changed, 159 insertions(+), 91 deletions(-) create mode 100644 rust/src/java/native_callbacks/sound.rs 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 a5e36c5..9c89f11 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java @@ -19,6 +19,7 @@ public class NativePatchBukkit { private static MethodHandle getWorldNative; private static MethodHandle freeStringNative; private static MethodHandle getRegistryDataNative; + private static MethodHandle entityPlaySoundNative; // Struct layout matching Rust's #[repr(C)] AbilitiesFFI private static final StructLayout ABILITIES_LAYOUT = @@ -186,7 +187,8 @@ public class NativePatchBukkit { long getLocationAddr, long freeStringAddr, long getWorldAddr, - long getRegistryDataAddr + long getRegistryDataAddr, + long entityPlaySoundAddr ) { // void rust_send_message(const char* uuid, const char* message) sendMessageNative = LINKER.downcallHandle( @@ -247,6 +249,20 @@ public class NativePatchBukkit { MemorySegment.ofAddress(getRegistryDataAddr), FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS) ); + + // void rust_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), + FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, // player_uuid + ValueLayout.ADDRESS, // sound_name + ValueLayout.ADDRESS, // sound_category + ValueLayout.ADDRESS, // entity_uuid + ValueLayout.JAVA_FLOAT, // volume + ValueLayout.JAVA_FLOAT // pitch + ) + ); } /** @@ -430,4 +446,41 @@ public class NativePatchBukkit { throw new RuntimeException("Failed to get registry data: " + registryName, t); } } + + /** + * Play a sound effect attached to an entity, 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 entityUuid The entity the sound is attached to + * @param volume Volume (1.0 = normal) + * @param pitch Pitch (1.0 = normal) + */ + public static void entityPlaySound( + UUID playerUuid, + String soundName, + String soundCategory, + UUID entityUuid, + 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); + MemorySegment entityUuidStr = arena.allocateFrom(entityUuid.toString()); + + entityPlaySoundNative.invokeExact( + playerUuidStr, + soundNameStr, + soundCategoryStr, + entityUuidStr, + volume, + pitch + ); + } catch (Throwable t) { + throw new RuntimeException("Failed to call native entityPlaySound", 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 e1d25f6..903fe44 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java @@ -711,8 +711,7 @@ public class PatchBukkitPlayer @Override public void playSound(Entity entity, Sound sound, SoundCategory category, float volume, float pitch) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'playSound'"); + NativePatchBukkit.entityPlaySound(this.uuid, sound.toString(), category.name(), entity.getUniqueId(), volume, pitch); } @Override diff --git a/rust/Cargo.lock b/rust/Cargo.lock index b195d2f..74c0f15 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1695,8 +1695,10 @@ dependencies = [ "log", "pumpkin", "pumpkin-api-macros", - "pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master)", - "pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-data", + "pumpkin-protocol", + "pumpkin-util", + "rand", "rust-embed", "serde", "serde-saphyr", @@ -1964,7 +1966,7 @@ dependencies = [ [[package]] name = "pumpkin" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "arc-swap", "base64", @@ -1979,12 +1981,12 @@ dependencies = [ "num-bigint", "pkcs8 0.11.0-rc.10", "pumpkin-config", - "pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-data", "pumpkin-inventory", "pumpkin-macros", - "pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-nbt", "pumpkin-protocol", - "pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-util", "pumpkin-world", "rand", "rsa", @@ -2007,7 +2009,7 @@ dependencies = [ [[package]] name = "pumpkin-api-macros" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "proc-macro-error2", "proc-macro2", @@ -2018,10 +2020,10 @@ dependencies = [ [[package]] name = "pumpkin-config" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "log", - "pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-util", "serde", "toml", "uuid", @@ -2030,35 +2032,15 @@ dependencies = [ [[package]] name = "pumpkin-data" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master#007b97eed8243cf744cb0353b12438db8eab2938" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "crc-fast", "heck", "indexmap", "phf", "proc-macro2", - "pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master)", - "pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master)", - "quote", - "rayon", - "serde", - "serde_json", - "serde_json5", - "syn", -] - -[[package]] -name = "pumpkin-data" -version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" -dependencies = [ - "crc-fast", - "heck", - "indexmap", - "phf", - "proc-macro2", - "pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", - "pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-nbt", + "pumpkin-util", "quote", "rayon", "serde", @@ -2070,13 +2052,13 @@ dependencies = [ [[package]] name = "pumpkin-inventory" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "crossbeam-utils", "log", - "pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-data", "pumpkin-protocol", - "pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-util", "pumpkin-world", "thiserror", "tokio", @@ -2085,11 +2067,11 @@ dependencies = [ [[package]] name = "pumpkin-macros" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "proc-macro-error2", "proc-macro2", - "pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-data", "quote", "syn", ] @@ -2097,19 +2079,7 @@ dependencies = [ [[package]] name = "pumpkin-nbt" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master#007b97eed8243cf744cb0353b12438db8eab2938" -dependencies = [ - "bytes", - "cesu8", - "flate2", - "serde", - "thiserror", -] - -[[package]] -name = "pumpkin-nbt" -version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "bytes", "cesu8", @@ -2121,17 +2091,17 @@ dependencies = [ [[package]] name = "pumpkin-protocol" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "aes", "async-compression", "bitflags", "bytes", "cfb8", - "pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-data", "pumpkin-macros", - "pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", - "pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-nbt", + "pumpkin-util", "pumpkin-world", "serde", "take_mut", @@ -2143,7 +2113,7 @@ dependencies = [ [[package]] name = "pumpkin-util" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master#007b97eed8243cf744cb0353b12438db8eab2938" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "base64", "bytes", @@ -2155,32 +2125,7 @@ dependencies = [ "num-traits", "p384", "proc-macro2", - "pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master)", - "quote", - "serde", - "serde_json", - "syn", - "thiserror", - "tokio", - "uuid", -] - -[[package]] -name = "pumpkin-util" -version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" -dependencies = [ - "base64", - "bytes", - "colored", - "ecdsa", - "enum_dispatch", - "md5", - "num-derive", - "num-traits", - "p384", - "proc-macro2", - "pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-nbt", "quote", "serde", "serde_json", @@ -2193,7 +2138,7 @@ dependencies = [ [[package]] name = "pumpkin-world" version = "0.1.0-dev+1.21.11" -source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a" +source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#441040105bb8ceb8b118bf84039727e6423046aa" dependencies = [ "bitflags", "bytes", @@ -2211,9 +2156,9 @@ dependencies = [ "num-traits", "num_cpus", "pumpkin-config", - "pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", - "pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", - "pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)", + "pumpkin-data", + "pumpkin-nbt", + "pumpkin-util", "rand", "rustc-hash", "ruzstd", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 7e38835..8481296 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,7 +8,8 @@ crate-type = ["cdylib"] [dependencies] pumpkin = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin" } -pumpkin-data = { git = "https://github.com/BjornTheProgrammer/Pumpkin.git", branch = "master", package = "pumpkin-data" } +pumpkin-data = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin-data" } +pumpkin-protocol = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin-protocol" } pumpkin-util = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin-util" } pumpkin-api-macros = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin-api-macros" } @@ -28,6 +29,8 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde-saphyr = "0.0.16" +rand = { git = "https://github.com/rust-random/rand"} + [build-dependencies] j4rs = { version = "0.24", artifact = "cdylib", lib = true } env_logger = "0.11.8" diff --git a/rust/src/java/native_callbacks/message.rs b/rust/src/java/native_callbacks/message.rs index 8931367..18e6275 100644 --- a/rust/src/java/native_callbacks/message.rs +++ b/rust/src/java/native_callbacks/message.rs @@ -4,7 +4,6 @@ use pumpkin_util::text::TextComponent; use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; -#[unsafe(no_mangle)] pub extern "C" fn rust_send_message(uuid_ptr: *const c_char, message_ptr: *const c_char) { let uuid_str = get_string(uuid_ptr); let message = get_string(message_ptr); diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index bb6892d..a221217 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -10,6 +10,7 @@ pub mod location; pub mod memory; pub mod message; pub mod registry; +pub mod sound; pub mod utils; pub mod world; @@ -44,6 +45,7 @@ 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; jvm.invoke_static( "org.patchbukkit.bridge.NativePatchBukkit", @@ -57,6 +59,7 @@ 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()?, ], )?; diff --git a/rust/src/java/native_callbacks/registry.rs b/rust/src/java/native_callbacks/registry.rs index 5118079..5f94481 100644 --- a/rust/src/java/native_callbacks/registry.rs +++ b/rust/src/java/native_callbacks/registry.rs @@ -17,7 +17,6 @@ impl Registry { } } -#[unsafe(no_mangle)] pub extern "C" fn rust_get_registry_data(registry_name: *const c_char) -> *const c_char { let name = get_string(registry_name); diff --git a/rust/src/java/native_callbacks/sound.rs b/rust/src/java/native_callbacks/sound.rs new file mode 100644 index 0000000..dcad2cf --- /dev/null +++ b/rust/src/java/native_callbacks/sound.rs @@ -0,0 +1,67 @@ +use std::ffi::{c_char, c_float}; + +use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector}; +use pumpkin_data::sound::{Sound, SoundCategory}; +use pumpkin_protocol::{IdOr, java::client::play::CEntitySoundEffect}; +use rand::{RngExt, rng}; + +use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; + +pub extern "C" fn rust_entity_play_sound( + player_uuid_ptr: *const c_char, + sound_name_ptr: *const c_char, + sound_category_ptr: *const c_char, + entity_uuid_ptr: *const c_char, + 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); + let entity_uuid_str = get_string(entity_uuid_ptr); + + if let Some(ctx) = CALLBACK_CONTEXT.get() { + let player_uuid = uuid::Uuid::parse_str(&player_uuid_str).unwrap(); + let entity_uuid = uuid::Uuid::parse_str(&entity_uuid_str).unwrap(); + + ctx.runtime.spawn(async move { + let player = ctx.plugin_context.server.get_player_by_uuid(player_uuid); + + let entity = ctx.plugin_context.server.select_entities( + &TargetSelector::new(EntitySelectorType::Uuid(entity_uuid)), + None, + ); + if entity.len() != 1 { + return; + } + + let entity = entity.first().unwrap().get_entity(); + + 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) { + Some(category) => category, + None => return, + }; + + let seed = rng().random::(); + + player + .client + .enqueue_packet(&CEntitySoundEffect::new( + IdOr::Id(sound as u16), + category, + entity.entity_id.into(), + volume, + pitch, + seed, + )) + .await; + } + }); + } +}