Get sound working

This commit is contained in:
Bjorn Beishline 2026-02-01 11:28:19 -07:00
parent 185822a5fe
commit 4b5f07b37c
6 changed files with 132 additions and 19 deletions

View File

@ -19,7 +19,8 @@ public class NativePatchBukkit {
private static MethodHandle getWorldNative; private static MethodHandle getWorldNative;
private static MethodHandle freeStringNative; private static MethodHandle freeStringNative;
private static MethodHandle getRegistryDataNative; private static MethodHandle getRegistryDataNative;
private static MethodHandle entityPlaySoundNative; private static MethodHandle playerEntityPlaySoundNative;
private static MethodHandle playerPlaySoundNative;
// Struct layout matching Rust's #[repr(C)] AbilitiesFFI // Struct layout matching Rust's #[repr(C)] AbilitiesFFI
private static final StructLayout ABILITIES_LAYOUT = private static final StructLayout ABILITIES_LAYOUT =
@ -188,7 +189,8 @@ public class NativePatchBukkit {
long freeStringAddr, long freeStringAddr,
long getWorldAddr, long getWorldAddr,
long getRegistryDataAddr, long getRegistryDataAddr,
long entityPlaySoundAddr long playerEntityPlaySoundAddr,
long playerPlaySoundAddr
) { ) {
// void rust_send_message(const char* uuid, const char* message) // void rust_send_message(const char* uuid, const char* message)
sendMessageNative = LINKER.downcallHandle( sendMessageNative = LINKER.downcallHandle(
@ -250,10 +252,10 @@ public class NativePatchBukkit {
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS) 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) // const char* sound_category, const char* entity_uuid, float volume, float pitch)
entityPlaySoundNative = LINKER.downcallHandle( playerEntityPlaySoundNative = LINKER.downcallHandle(
MemorySegment.ofAddress(entityPlaySoundAddr), MemorySegment.ofAddress(playerEntityPlaySoundAddr),
FunctionDescriptor.ofVoid( FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS, // player_uuid ValueLayout.ADDRESS, // player_uuid
ValueLayout.ADDRESS, // sound_name ValueLayout.ADDRESS, // sound_name
@ -263,6 +265,22 @@ public class NativePatchBukkit {
ValueLayout.JAVA_FLOAT // pitch 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 volume Volume (1.0 = normal)
* @param pitch Pitch (1.0 = normal) * @param pitch Pitch (1.0 = normal)
*/ */
public static void entityPlaySound( public static void playerEntityPlaySound(
UUID playerUuid, UUID playerUuid,
String soundName, String soundName,
String soundCategory, String soundCategory,
@ -471,7 +489,7 @@ public class NativePatchBukkit {
MemorySegment soundCategoryStr = arena.allocateFrom(soundCategory); MemorySegment soundCategoryStr = arena.allocateFrom(soundCategory);
MemorySegment entityUuidStr = arena.allocateFrom(entityUuid.toString()); MemorySegment entityUuidStr = arena.allocateFrom(entityUuid.toString());
entityPlaySoundNative.invokeExact( playerEntityPlaySoundNative.invokeExact(
playerUuidStr, playerUuidStr,
soundNameStr, soundNameStr,
soundCategoryStr, soundCategoryStr,
@ -483,4 +501,46 @@ public class NativePatchBukkit {
throw new RuntimeException("Failed to call native entityPlaySound", t); 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);
}
}
} }

View File

@ -62,6 +62,7 @@ import org.jetbrains.annotations.Unmodifiable;
import org.jetbrains.annotations.UnmodifiableView; import org.jetbrains.annotations.UnmodifiableView;
import org.patchbukkit.PatchBukkitServer; import org.patchbukkit.PatchBukkitServer;
import org.patchbukkit.bridge.NativePatchBukkit; import org.patchbukkit.bridge.NativePatchBukkit;
import org.patchbukkit.registry.PatchBukkitSound;
import com.destroystokyo.paper.ClientOption; import com.destroystokyo.paper.ClientOption;
import com.destroystokyo.paper.Title; import com.destroystokyo.paper.Title;
@ -685,14 +686,13 @@ public class PatchBukkitPlayer
@Override @Override
public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) { public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) {
// TODO Auto-generated method stub var patchBukkitSound = (PatchBukkitSound) sound;
throw new UnsupportedOperationException("Unimplemented method 'playSound'"); NativePatchBukkit.playerPlaySound(this.uuid, patchBukkitSound.getOriginalName(), category.name(), location.x(), location.y(), location.z(), volume, pitch);
} }
@Override @Override
public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) { public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) {
// TODO Auto-generated method stub NativePatchBukkit.playerPlaySound(this.uuid, sound, category.name(), location.x(), location.y(), location.z(), volume, pitch);
throw new UnsupportedOperationException("Unimplemented method 'playSound'");
} }
@Override @Override
@ -711,13 +711,13 @@ public class PatchBukkitPlayer
@Override @Override
public void playSound(Entity entity, Sound sound, SoundCategory category, float volume, float pitch) { 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 @Override
public void playSound(Entity entity, String sound, SoundCategory category, float volume, float pitch) { public void playSound(Entity entity, String sound, SoundCategory category, float volume, float pitch) {
// TODO Auto-generated method stub NativePatchBukkit.playerEntityPlaySound(this.uuid, sound, category.name(), entity.getUniqueId(), volume, pitch);
throw new UnsupportedOperationException("Unimplemented method 'playSound'");
} }
@Override @Override

View File

@ -9,15 +9,21 @@ public class PatchBukkitSound implements Sound {
private final NamespacedKey namespacedKey; private final NamespacedKey namespacedKey;
private final Key adventureKey; private final Key adventureKey;
private final String enumName; private final String enumName;
private final String originalName;
private final int protocolId; private final int protocolId;
public PatchBukkitSound(String soundName, int protocolId) { public PatchBukkitSound(String soundName, int protocolId) {
this.namespacedKey = new NamespacedKey(NamespacedKey.MINECRAFT, soundName); this.namespacedKey = new NamespacedKey(NamespacedKey.MINECRAFT, soundName);
this.adventureKey = Key.key(Key.MINECRAFT_NAMESPACE, soundName); this.adventureKey = Key.key(Key.MINECRAFT_NAMESPACE, soundName);
this.protocolId = protocolId; this.protocolId = protocolId;
this.originalName = soundName;
this.enumName = soundName.toUpperCase().replace('.', '_'); this.enumName = soundName.toUpperCase().replace('.', '_');
} }
public String getOriginalName() {
return originalName;
}
@Override @Override
public NamespacedKey getKey() { public NamespacedKey getKey() {
return namespacedKey; return namespacedKey;

View File

@ -45,7 +45,9 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
let free_string_addr = memory::rust_free_string as *const () as i64; 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 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_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( jvm.invoke_static(
"org.patchbukkit.bridge.NativePatchBukkit", "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(free_string_addr)?.into_primitive()?,
InvocationArg::try_from(get_world_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_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()?,
], ],
)?; )?;

View File

@ -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::command::args::entities::{EntitySelectorType, TargetSelector};
use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_protocol::{IdOr, java::client::play::CEntitySoundEffect}; use pumpkin_protocol::{IdOr, java::client::play::CEntitySoundEffect};
use pumpkin_util::math::vector3::Vector3;
use rand::{RngExt, rng}; use rand::{RngExt, rng};
use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; 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, player_uuid_ptr: *const c_char,
sound_name_ptr: *const c_char, sound_name_ptr: *const c_char,
sound_category_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::<f64>();
let position: Vector3<f64> = Vector3::new(x, y, z);
player
.play_sound(sound as u16, category, &position, volume, pitch, seed)
.await;
}
});
}
}

View File

@ -4,7 +4,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use anyhow::{Context, Result}; use anyhow::Result;
use rust_embed::Embed; use rust_embed::Embed;
#[derive(Embed)] #[derive(Embed)]