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.VarHandle;
import java.util.UUID;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin;
public class NativePatchBukkit {
private static final Linker LINKER = Linker.nativeLinker();
private static MethodHandle sendMessageNative;
@ -18,33 +18,41 @@ public class NativePatchBukkit {
private static MethodHandle getLocationNative;
// Struct layout matching Rust's #[repr(C)] AbilitiesFFI
private static final StructLayout ABILITIES_LAYOUT = MemoryLayout.structLayout(
ValueLayout.JAVA_BOOLEAN.withName("invulnerable"),
ValueLayout.JAVA_BOOLEAN.withName("flying"),
ValueLayout.JAVA_BOOLEAN.withName("allow_flying"),
ValueLayout.JAVA_BOOLEAN.withName("creative"),
ValueLayout.JAVA_BOOLEAN.withName("allow_modify_world"),
MemoryLayout.paddingLayout(3), // Padding to align f32 to 4-byte boundary
ValueLayout.JAVA_FLOAT.withName("fly_speed"),
ValueLayout.JAVA_FLOAT.withName("walk_speed")
);
private static final StructLayout ABILITIES_LAYOUT =
MemoryLayout.structLayout(
ValueLayout.JAVA_BOOLEAN.withName("invulnerable"),
ValueLayout.JAVA_BOOLEAN.withName("flying"),
ValueLayout.JAVA_BOOLEAN.withName("allow_flying"),
ValueLayout.JAVA_BOOLEAN.withName("creative"),
ValueLayout.JAVA_BOOLEAN.withName("allow_modify_world"),
MemoryLayout.paddingLayout(3), // Padding to align f32 to 4-byte boundary
ValueLayout.JAVA_FLOAT.withName("fly_speed"),
ValueLayout.JAVA_FLOAT.withName("walk_speed")
);
// VarHandles for accessing struct fields
private static final VarHandle INVULNERABLE = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("invulnerable"));
MemoryLayout.PathElement.groupElement("invulnerable")
);
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(
MemoryLayout.PathElement.groupElement("allow_flying"));
MemoryLayout.PathElement.groupElement("allow_flying")
);
private static final VarHandle CREATIVE = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("creative"));
private static final VarHandle ALLOW_MODIFY_WORLD = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("allow_modify_world"));
MemoryLayout.PathElement.groupElement("creative")
);
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(
MemoryLayout.PathElement.groupElement("fly_speed"));
MemoryLayout.PathElement.groupElement("fly_speed")
);
private static final VarHandle WALK_SPEED = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("walk_speed"));
MemoryLayout.PathElement.groupElement("walk_speed")
);
// Struct layout for Vec3
private static final StructLayout VEC3_LAYOUT = MemoryLayout.structLayout(
@ -55,61 +63,125 @@ public class NativePatchBukkit {
// VarHandles for Vec3 fields
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(
MemoryLayout.PathElement.groupElement("y"));
MemoryLayout.PathElement.groupElement("y")
);
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) {}
/**
* Java record to hold player abilities.
*/
public record Abilities(
boolean invulnerable,
boolean flying,
boolean allowFlying,
boolean creative,
boolean allowModifyWorld,
float flySpeed,
float walkSpeed
) {
// "Wither" methods - return a copy with one field changed
public Abilities withInvulnerable(boolean value) {
return new Abilities(value, flying, allowFlying, creative, allowModifyWorld, flySpeed, walkSpeed);
}
public record Abilities(
boolean invulnerable,
boolean flying,
boolean allowFlying,
boolean creative,
boolean allowModifyWorld,
float flySpeed,
float walkSpeed
) {
// "Wither" methods - return a copy with one field changed
public Abilities withInvulnerable(boolean value) {
return new Abilities(
value,
flying,
allowFlying,
creative,
allowModifyWorld,
flySpeed,
walkSpeed
);
}
public Abilities withFlying(boolean value) {
return new Abilities(invulnerable, value, allowFlying, creative, allowModifyWorld, flySpeed, walkSpeed);
}
public Abilities withFlying(boolean value) {
return new Abilities(
invulnerable,
value,
allowFlying,
creative,
allowModifyWorld,
flySpeed,
walkSpeed
);
}
public Abilities withAllowFlying(boolean value) {
return new Abilities(invulnerable, flying, value, creative, allowModifyWorld, flySpeed, walkSpeed);
}
public Abilities withAllowFlying(boolean value) {
return new Abilities(
invulnerable,
flying,
value,
creative,
allowModifyWorld,
flySpeed,
walkSpeed
);
}
public Abilities withCreative(boolean value) {
return new Abilities(invulnerable, flying, allowFlying, value, allowModifyWorld, flySpeed, walkSpeed);
}
public Abilities withCreative(boolean value) {
return new Abilities(
invulnerable,
flying,
allowFlying,
value,
allowModifyWorld,
flySpeed,
walkSpeed
);
}
public Abilities withAllowModifyWorld(boolean value) {
return new Abilities(invulnerable, flying, allowFlying, creative, value, flySpeed, walkSpeed);
}
public Abilities withAllowModifyWorld(boolean value) {
return new Abilities(
invulnerable,
flying,
allowFlying,
creative,
value,
flySpeed,
walkSpeed
);
}
public Abilities withFlySpeed(float value) {
return new Abilities(invulnerable, flying, allowFlying, creative, allowModifyWorld, value, walkSpeed);
}
public Abilities withFlySpeed(float value) {
return new Abilities(
invulnerable,
flying,
allowFlying,
creative,
allowModifyWorld,
value,
walkSpeed
);
}
public Abilities withWalkSpeed(float value) {
return new Abilities(invulnerable, flying, allowFlying, creative, allowModifyWorld, flySpeed, value);
}
}
public Abilities withWalkSpeed(float value) {
return new Abilities(
invulnerable,
flying,
allowFlying,
creative,
allowModifyWorld,
flySpeed,
value
);
}
}
/**
* 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)
sendMessageNative = LINKER.downcallHandle(
MemorySegment.ofAddress(sendMessageAddr),
@ -126,9 +198,9 @@ public class NativePatchBukkit {
getAbilitiesNative = LINKER.downcallHandle(
MemorySegment.ofAddress(getAbilitiesAddr),
FunctionDescriptor.of(
ValueLayout.JAVA_BOOLEAN, // return type
ValueLayout.ADDRESS, // uuid string
ValueLayout.ADDRESS // out pointer to AbilitiesFFI
ValueLayout.JAVA_BOOLEAN, // return type
ValueLayout.ADDRESS, // uuid string
ValueLayout.ADDRESS // out pointer to AbilitiesFFI
)
);
@ -136,9 +208,9 @@ public class NativePatchBukkit {
setAbilitiesNative = LINKER.downcallHandle(
MemorySegment.ofAddress(setAbilitiesAddr),
FunctionDescriptor.of(
ValueLayout.JAVA_BOOLEAN, // return type
ValueLayout.ADDRESS, // uuid string
ValueLayout.ADDRESS // pointer to AbilitiesFFI
ValueLayout.JAVA_BOOLEAN, // return type
ValueLayout.ADDRESS, // uuid string
ValueLayout.ADDRESS // pointer to AbilitiesFFI
)
);
@ -146,9 +218,9 @@ public class NativePatchBukkit {
getLocationNative = LINKER.downcallHandle(
MemorySegment.ofAddress(getLocationAddr),
FunctionDescriptor.of(
ValueLayout.JAVA_BOOLEAN, // return type
ValueLayout.ADDRESS, // uuid string
ValueLayout.ADDRESS // out pointer to Vec3FFI
ValueLayout.JAVA_BOOLEAN, // return type
ValueLayout.ADDRESS, // uuid string
ValueLayout.ADDRESS // out pointer to Vec3FFI
)
);
}
@ -177,7 +249,9 @@ public class NativePatchBukkit {
// Simple approach - pass plugin name, handle lookup in Rust
try (Arena arena = Arena.ofConfined()) {
MemorySegment pluginName = arena.allocateFrom(plugin.getName());
MemorySegment listenerClass = arena.allocateFrom(listener.getClass().getName());
MemorySegment listenerClass = arena.allocateFrom(
listener.getClass().getName()
);
registerEventNative.invokeExact(listenerClass, pluginName);
} catch (Throwable t) {
throw new RuntimeException("Failed to register event", t);
@ -195,7 +269,10 @@ public class NativePatchBukkit {
MemorySegment uuidStr = arena.allocateFrom(uuid.toString());
MemorySegment outStruct = arena.allocate(ABILITIES_LAYOUT);
boolean success = (boolean) getAbilitiesNative.invokeExact(uuidStr, outStruct);
boolean success = (boolean) getAbilitiesNative.invokeExact(
uuidStr,
outStruct
);
if (!success) {
return null;
@ -225,11 +302,18 @@ public class NativePatchBukkit {
FLYING.set(abilitiesStruct, 0L, abilities.flying());
ALLOW_FLYING.set(abilitiesStruct, 0L, abilities.allowFlying());
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());
WALK_SPEED.set(abilitiesStruct, 0L, abilities.walkSpeed());
return (boolean) setAbilitiesNative.invokeExact(uuidStr, abilitiesStruct);
return (boolean) setAbilitiesNative.invokeExact(
uuidStr,
abilitiesStruct
);
} catch (Throwable t) {
throw new RuntimeException("Failed to call native setAbilities", t);
}
@ -246,7 +330,10 @@ public class NativePatchBukkit {
MemorySegment uuidStr = arena.allocateFrom(uuid.toString());
MemorySegment outStruct = arena.allocate(VEC3_LAYOUT);
boolean success = (boolean) getLocationNative.invokeExact(uuidStr, outStruct);
boolean success = (boolean) getLocationNative.invokeExact(
uuidStr,
outStruct
);
if (!success) {
return null;

8
rust/Cargo.lock generated
View File

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

View File

@ -1,5 +1,7 @@
use std::ffi::c_char;
use pumpkin::command::args::entities::{EntitySelectorType, TargetSelector};
use crate::java::native_callbacks::{CALLBACK_CONTEXT, utils::get_string};
#[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);
if let Some(ctx) = CALLBACK_CONTEXT.get() {
let uuid = uuid::Uuid::parse_str(&uuid_str).unwrap();
let player = ctx.plugin_context.server.get_player_by_uuid(uuid);
if let Some(player) = player {
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 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 {
(*out).x = position.x;

View File

@ -9,6 +9,7 @@ pub mod events;
pub mod location;
pub mod message;
pub mod utils;
pub mod world;
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()
}