Use entity instead of player

This commit is contained in:
Bjorn Beishline 2026-01-31 12:50:51 -07:00
parent 26b9727166
commit 4a906ae3dc
5 changed files with 199 additions and 78 deletions

View File

@ -4,11 +4,11 @@ import java.lang.foreign.*;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle;
import java.util.UUID; import java.util.UUID;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
public class NativePatchBukkit { public class NativePatchBukkit {
private static final Linker LINKER = Linker.nativeLinker(); private static final Linker LINKER = Linker.nativeLinker();
private static MethodHandle sendMessageNative; private static MethodHandle sendMessageNative;
@ -18,7 +18,8 @@ public class NativePatchBukkit {
private static MethodHandle getLocationNative; private static MethodHandle getLocationNative;
// Struct layout matching Rust's #[repr(C)] AbilitiesFFI // Struct layout matching Rust's #[repr(C)] AbilitiesFFI
private static final StructLayout ABILITIES_LAYOUT = MemoryLayout.structLayout( private static final StructLayout ABILITIES_LAYOUT =
MemoryLayout.structLayout(
ValueLayout.JAVA_BOOLEAN.withName("invulnerable"), ValueLayout.JAVA_BOOLEAN.withName("invulnerable"),
ValueLayout.JAVA_BOOLEAN.withName("flying"), ValueLayout.JAVA_BOOLEAN.withName("flying"),
ValueLayout.JAVA_BOOLEAN.withName("allow_flying"), ValueLayout.JAVA_BOOLEAN.withName("allow_flying"),
@ -31,20 +32,27 @@ public class NativePatchBukkit {
// VarHandles for accessing struct fields // VarHandles for accessing struct fields
private static final VarHandle INVULNERABLE = ABILITIES_LAYOUT.varHandle( private static final VarHandle INVULNERABLE = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("invulnerable")); MemoryLayout.PathElement.groupElement("invulnerable")
);
private static final VarHandle FLYING = ABILITIES_LAYOUT.varHandle( private static final VarHandle FLYING = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("flying")); MemoryLayout.PathElement.groupElement("flying")
);
private static final VarHandle ALLOW_FLYING = ABILITIES_LAYOUT.varHandle( private static final VarHandle ALLOW_FLYING = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("allow_flying")); MemoryLayout.PathElement.groupElement("allow_flying")
);
private static final VarHandle CREATIVE = ABILITIES_LAYOUT.varHandle( private static final VarHandle CREATIVE = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("creative")); MemoryLayout.PathElement.groupElement("creative")
private static final VarHandle ALLOW_MODIFY_WORLD = ABILITIES_LAYOUT.varHandle( );
MemoryLayout.PathElement.groupElement("allow_modify_world")); private static final VarHandle ALLOW_MODIFY_WORLD =
ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("allow_modify_world")
);
private static final VarHandle FLY_SPEED = ABILITIES_LAYOUT.varHandle( private static final VarHandle FLY_SPEED = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("fly_speed")); MemoryLayout.PathElement.groupElement("fly_speed")
);
private static final VarHandle WALK_SPEED = ABILITIES_LAYOUT.varHandle( private static final VarHandle WALK_SPEED = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("walk_speed")); MemoryLayout.PathElement.groupElement("walk_speed")
);
// Struct layout for Vec3 // Struct layout for Vec3
private static final StructLayout VEC3_LAYOUT = MemoryLayout.structLayout( private static final StructLayout VEC3_LAYOUT = MemoryLayout.structLayout(
@ -55,15 +63,17 @@ public class NativePatchBukkit {
// VarHandles for Vec3 fields // VarHandles for Vec3 fields
private static final VarHandle VEC3_X = VEC3_LAYOUT.varHandle( private static final VarHandle VEC3_X = VEC3_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("x")); MemoryLayout.PathElement.groupElement("x")
);
private static final VarHandle VEC3_Y = VEC3_LAYOUT.varHandle( private static final VarHandle VEC3_Y = VEC3_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("y")); MemoryLayout.PathElement.groupElement("y")
);
private static final VarHandle VEC3_Z = VEC3_LAYOUT.varHandle( private static final VarHandle VEC3_Z = VEC3_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("z")); MemoryLayout.PathElement.groupElement("z")
);
public record Vec3(double x, double y, double z) {} public record Vec3(double x, double y, double z) {}
/** /**
* Java record to hold player abilities. * Java record to hold player abilities.
*/ */
@ -78,38 +88,100 @@ public class NativePatchBukkit {
) { ) {
// "Wither" methods - return a copy with one field changed // "Wither" methods - return a copy with one field changed
public Abilities withInvulnerable(boolean value) { public Abilities withInvulnerable(boolean value) {
return new Abilities(value, flying, allowFlying, creative, allowModifyWorld, flySpeed, walkSpeed); return new Abilities(
value,
flying,
allowFlying,
creative,
allowModifyWorld,
flySpeed,
walkSpeed
);
} }
public Abilities withFlying(boolean value) { public Abilities withFlying(boolean value) {
return new Abilities(invulnerable, value, allowFlying, creative, allowModifyWorld, flySpeed, walkSpeed); return new Abilities(
invulnerable,
value,
allowFlying,
creative,
allowModifyWorld,
flySpeed,
walkSpeed
);
} }
public Abilities withAllowFlying(boolean value) { public Abilities withAllowFlying(boolean value) {
return new Abilities(invulnerable, flying, value, creative, allowModifyWorld, flySpeed, walkSpeed); return new Abilities(
invulnerable,
flying,
value,
creative,
allowModifyWorld,
flySpeed,
walkSpeed
);
} }
public Abilities withCreative(boolean value) { public Abilities withCreative(boolean value) {
return new Abilities(invulnerable, flying, allowFlying, value, allowModifyWorld, flySpeed, walkSpeed); return new Abilities(
invulnerable,
flying,
allowFlying,
value,
allowModifyWorld,
flySpeed,
walkSpeed
);
} }
public Abilities withAllowModifyWorld(boolean value) { public Abilities withAllowModifyWorld(boolean value) {
return new Abilities(invulnerable, flying, allowFlying, creative, value, flySpeed, walkSpeed); return new Abilities(
invulnerable,
flying,
allowFlying,
creative,
value,
flySpeed,
walkSpeed
);
} }
public Abilities withFlySpeed(float value) { public Abilities withFlySpeed(float value) {
return new Abilities(invulnerable, flying, allowFlying, creative, allowModifyWorld, value, walkSpeed); return new Abilities(
invulnerable,
flying,
allowFlying,
creative,
allowModifyWorld,
value,
walkSpeed
);
} }
public Abilities withWalkSpeed(float value) { public Abilities withWalkSpeed(float value) {
return new Abilities(invulnerable, flying, allowFlying, creative, allowModifyWorld, flySpeed, value); return new Abilities(
invulnerable,
flying,
allowFlying,
creative,
allowModifyWorld,
flySpeed,
value
);
} }
} }
/** /**
* Called from Rust during initialization to register native function pointers. * Called from Rust during initialization to register native function pointers.
*/ */
public static void initCallbacks(long sendMessageAddr, long registerEventAddr, long getAbilitiesAddr, long setAbilitiesAddr, long getLocationAddr) { public static void initCallbacks(
long sendMessageAddr,
long registerEventAddr,
long getAbilitiesAddr,
long setAbilitiesAddr,
long getLocationAddr
) {
// 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(
MemorySegment.ofAddress(sendMessageAddr), MemorySegment.ofAddress(sendMessageAddr),
@ -177,7 +249,9 @@ public class NativePatchBukkit {
// Simple approach - pass plugin name, handle lookup in Rust // Simple approach - pass plugin name, handle lookup in Rust
try (Arena arena = Arena.ofConfined()) { try (Arena arena = Arena.ofConfined()) {
MemorySegment pluginName = arena.allocateFrom(plugin.getName()); MemorySegment pluginName = arena.allocateFrom(plugin.getName());
MemorySegment listenerClass = arena.allocateFrom(listener.getClass().getName()); MemorySegment listenerClass = arena.allocateFrom(
listener.getClass().getName()
);
registerEventNative.invokeExact(listenerClass, pluginName); registerEventNative.invokeExact(listenerClass, pluginName);
} catch (Throwable t) { } catch (Throwable t) {
throw new RuntimeException("Failed to register event", t); throw new RuntimeException("Failed to register event", t);
@ -195,7 +269,10 @@ public class NativePatchBukkit {
MemorySegment uuidStr = arena.allocateFrom(uuid.toString()); MemorySegment uuidStr = arena.allocateFrom(uuid.toString());
MemorySegment outStruct = arena.allocate(ABILITIES_LAYOUT); MemorySegment outStruct = arena.allocate(ABILITIES_LAYOUT);
boolean success = (boolean) getAbilitiesNative.invokeExact(uuidStr, outStruct); boolean success = (boolean) getAbilitiesNative.invokeExact(
uuidStr,
outStruct
);
if (!success) { if (!success) {
return null; return null;
@ -225,11 +302,18 @@ public class NativePatchBukkit {
FLYING.set(abilitiesStruct, 0L, abilities.flying()); FLYING.set(abilitiesStruct, 0L, abilities.flying());
ALLOW_FLYING.set(abilitiesStruct, 0L, abilities.allowFlying()); ALLOW_FLYING.set(abilitiesStruct, 0L, abilities.allowFlying());
CREATIVE.set(abilitiesStruct, 0L, abilities.creative()); CREATIVE.set(abilitiesStruct, 0L, abilities.creative());
ALLOW_MODIFY_WORLD.set(abilitiesStruct, 0L, abilities.allowModifyWorld()); ALLOW_MODIFY_WORLD.set(
abilitiesStruct,
0L,
abilities.allowModifyWorld()
);
FLY_SPEED.set(abilitiesStruct, 0L, abilities.flySpeed()); FLY_SPEED.set(abilitiesStruct, 0L, abilities.flySpeed());
WALK_SPEED.set(abilitiesStruct, 0L, abilities.walkSpeed()); WALK_SPEED.set(abilitiesStruct, 0L, abilities.walkSpeed());
return (boolean) setAbilitiesNative.invokeExact(uuidStr, abilitiesStruct); return (boolean) setAbilitiesNative.invokeExact(
uuidStr,
abilitiesStruct
);
} catch (Throwable t) { } catch (Throwable t) {
throw new RuntimeException("Failed to call native setAbilities", t); throw new RuntimeException("Failed to call native setAbilities", t);
} }
@ -246,7 +330,10 @@ public class NativePatchBukkit {
MemorySegment uuidStr = arena.allocateFrom(uuid.toString()); MemorySegment uuidStr = arena.allocateFrom(uuid.toString());
MemorySegment outStruct = arena.allocate(VEC3_LAYOUT); MemorySegment outStruct = arena.allocate(VEC3_LAYOUT);
boolean success = (boolean) getLocationNative.invokeExact(uuidStr, outStruct); boolean success = (boolean) getLocationNative.invokeExact(
uuidStr,
outStruct
);
if (!success) { if (!success) {
return null; return null;

8
rust/Cargo.lock generated
View File

@ -1875,15 +1875,15 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.13.0" version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]] [[package]]
name = "portable-atomic-util" name = "portable-atomic-util"
version = "0.2.4" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5"
dependencies = [ dependencies = [
"portable-atomic", "portable-atomic",
] ]

View File

@ -1,5 +1,7 @@
use std::ffi::c_char; use std::ffi::c_char;
use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector};
use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string}; use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string};
#[repr(C)] #[repr(C)]
@ -13,10 +15,14 @@ pub extern "C" fn rust_get_location(uuid_ptr: *const c_char, out: *mut Vec3FFI)
let uuid_str = get_string(uuid_ptr); let uuid_str = get_string(uuid_ptr);
if let Some(ctx) = CALLBACK_CONTEXT.get() { if let Some(ctx) = CALLBACK_CONTEXT.get() {
let uuid = uuid::Uuid::parse_str(&uuid_str).unwrap(); let uuid = uuid::Uuid::parse_str(&uuid_str).unwrap();
let player = ctx.plugin_context.server.get_player_by_uuid(uuid); let entity = ctx
if let Some(player) = player { .plugin_context
.server
.select_entities(&TargetSelector::new(EntitySelectorType::Uuid(uuid)), None);
if entity.len() == 1 {
let entity = entity.first().unwrap().get_entity();
let position = let position =
tokio::task::block_in_place(|| ctx.runtime.block_on(async { player.position() })); tokio::task::block_in_place(|| ctx.runtime.block_on(async { entity.pos.load() }));
unsafe { unsafe {
(*out).x = position.x; (*out).x = position.x;

View File

@ -9,6 +9,7 @@ pub mod events;
pub mod location; pub mod location;
pub mod message; pub mod message;
pub mod utils; pub mod utils;
pub mod world;
static CALLBACK_CONTEXT: OnceLock<CallbackContext> = OnceLock::new(); static CALLBACK_CONTEXT: OnceLock<CallbackContext> = OnceLock::new();

View File

@ -0,0 +1,27 @@
use std::ffi::{CString, c_char};
use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector};
use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string};
pub extern "C" fn rust_get_world(uuid_ptr: *const c_char) -> *const c_char {
let uuid_str = get_string(uuid_ptr);
if let Some(ctx) = CALLBACK_CONTEXT.get() {
let uuid = uuid::Uuid::parse_str(&uuid_str).unwrap();
let entity = ctx
.plugin_context
.server
.select_entities(&TargetSelector::new(EntitySelectorType::Uuid(uuid)), None);
if entity.len() == 1 {
let entity = entity.first().unwrap().get_entity();
let world = entity.world.load();
let cstring = match CString::new(world.dimension.minecraft_name) {
Ok(s) => s,
Err(_) => return std::ptr::null(),
};
}
}
std::ptr::null()
}