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 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);
}
}
}

View File

@ -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

View File

@ -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;

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 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()?,
],
)?;

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_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::<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},
};
use anyhow::{Context, Result};
use anyhow::Result;
use rust_embed::Embed;
#[derive(Embed)]