Implement get and set abilities

This commit is contained in:
Bjorn Beishline 2026-01-31 05:31:22 -07:00
parent 20c823eb76
commit b4521e1c8e
4 changed files with 251 additions and 19 deletions

View File

@ -2,7 +2,9 @@ package org.patchbukkit.bridge;
import java.lang.foreign.*; import java.lang.foreign.*;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
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;
@ -11,9 +13,83 @@ public class NativePatchBukkit {
private static MethodHandle sendMessageNative; private static MethodHandle sendMessageNative;
private static MethodHandle registerEventNative; private static MethodHandle registerEventNative;
private static MethodHandle getAbilitiesNative;
private static MethodHandle setAbilitiesNative;
// Called from Rust during initialization // Struct layout matching Rust's #[repr(C)] AbilitiesFFI
public static void initCallbacks(long sendMessageAddr, long registerEventAddr) { 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"));
private static final VarHandle FLYING = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("flying"));
private static final VarHandle ALLOW_FLYING = ABILITIES_LAYOUT.varHandle(
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"));
private static final VarHandle FLY_SPEED = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("fly_speed"));
private static final VarHandle WALK_SPEED = ABILITIES_LAYOUT.varHandle(
MemoryLayout.PathElement.groupElement("walk_speed"));
/**
* 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 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 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 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);
}
}
/**
* Called from Rust during initialization to register native function pointers.
*/
public static void initCallbacks(long sendMessageAddr, long registerEventAddr, long getAbilitiesAddr, long setAbilitiesAddr) {
// 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),
@ -25,31 +101,108 @@ public class NativePatchBukkit {
MemorySegment.ofAddress(registerEventAddr), MemorySegment.ofAddress(registerEventAddr),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS) FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS)
); );
// bool rust_get_abilities(const char* uuid, AbilitiesFFI* out)
getAbilitiesNative = LINKER.downcallHandle(
MemorySegment.ofAddress(getAbilitiesAddr),
FunctionDescriptor.of(
ValueLayout.JAVA_BOOLEAN, // return type
ValueLayout.ADDRESS, // uuid string
ValueLayout.ADDRESS // out pointer to AbilitiesFFI
)
);
// bool rust_set_abilities(const char* uuid, AbilitiesFFI* abilities)
setAbilitiesNative = LINKER.downcallHandle(
MemorySegment.ofAddress(setAbilitiesAddr),
FunctionDescriptor.of(
ValueLayout.JAVA_BOOLEAN, // return type
ValueLayout.ADDRESS, // uuid string
ValueLayout.ADDRESS // pointer to AbilitiesFFI
)
);
} }
/**
* Send a message to a player by UUID.
*/
public static void sendMessage(UUID uuid, String message) { public static void sendMessage(UUID uuid, String message) {
try (Arena arena = Arena.ofConfined()) { try (Arena arena = Arena.ofConfined()) {
MemorySegment uuidStr = arena.allocateFrom(uuid.toString()); MemorySegment uuidStr = arena.allocateFrom(uuid.toString());
MemorySegment msgStr = arena.allocateFrom(message); MemorySegment msgStr = arena.allocateFrom(message);
sendMessageNative.invokeExact(uuidStr, msgStr); sendMessageNative.invokeExact(uuidStr, msgStr);
} catch (Throwable t) { } catch (Throwable t) {
throw new RuntimeException("Failed to call native sendMessage", t); throw new RuntimeException("Failed to call native sendMessage", t);
} }
} }
/**
* Register an event listener for a plugin.
*/
public static void registerEvent(Listener listener, Plugin plugin) { public static void registerEvent(Listener listener, Plugin plugin) {
// For objects, you have options: // For objects, you have options:
// 1. Pass identifying info (strings, IDs) instead of object refs // 1. Pass identifying info (strings, IDs) instead of object refs
// 2. Store objects in a Java-side registry and pass an ID // 2. Store objects in a Java-side registry and pass an ID
// 3. Use JNI NewGlobalRef from Rust side if you need to hold refs // 3. Use JNI NewGlobalRef from Rust side if you need to hold refs
// 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());
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);
} }
} }
/**
* Get a player's abilities by UUID.
*
* @param uuid The player's UUID
* @return The player's abilities, or null if player not found
*/
public static Abilities getAbilities(UUID uuid) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment uuidStr = arena.allocateFrom(uuid.toString());
MemorySegment outStruct = arena.allocate(ABILITIES_LAYOUT);
boolean success = (boolean) getAbilitiesNative.invokeExact(uuidStr, outStruct);
if (!success) {
return null;
}
return new Abilities(
(boolean) INVULNERABLE.get(outStruct, 0L),
(boolean) FLYING.get(outStruct, 0L),
(boolean) ALLOW_FLYING.get(outStruct, 0L),
(boolean) CREATIVE.get(outStruct, 0L),
(boolean) ALLOW_MODIFY_WORLD.get(outStruct, 0L),
(float) FLY_SPEED.get(outStruct, 0L),
(float) WALK_SPEED.get(outStruct, 0L)
);
} catch (Throwable t) {
throw new RuntimeException("Failed to call native getAbilities", t);
}
}
public static boolean setAbilities(UUID uuid, Abilities abilities) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment uuidStr = arena.allocateFrom(uuid.toString());
MemorySegment abilitiesStruct = arena.allocate(ABILITIES_LAYOUT);
// Populate the struct with values from the Abilities record
INVULNERABLE.set(abilitiesStruct, 0L, abilities.invulnerable());
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());
FLY_SPEED.set(abilitiesStruct, 0L, abilities.flySpeed());
WALK_SPEED.set(abilitiesStruct, 0L, abilities.walkSpeed());
return (boolean) setAbilitiesNative.invokeExact(uuidStr, abilitiesStruct);
} catch (Throwable t) {
throw new RuntimeException("Failed to call native setAbilities", t);
}
}
} }

View File

@ -1176,14 +1176,14 @@ public class PatchBukkitPlayer
@Override @Override
public boolean getAllowFlight() { public boolean getAllowFlight() {
// TODO Auto-generated method stub return NativePatchBukkit.getAbilities(this.uuid).allowFlying();
throw new UnsupportedOperationException("Unimplemented method 'getAllowFlight'");
} }
@Override @Override
public void setAllowFlight(boolean flight) { public void setAllowFlight(boolean flight) {
// TODO Auto-generated method stub var abilities = NativePatchBukkit.getAbilities(this.uuid);
throw new UnsupportedOperationException("Unimplemented method 'setAllowFlight'"); abilities.withAllowFlying(flight);
NativePatchBukkit.setAbilities(this.uuid, abilities);
} }
@Override @Override
@ -1260,8 +1260,9 @@ public class PatchBukkitPlayer
@Override @Override
public void setFlying(boolean value) { public void setFlying(boolean value) {
// TODO Auto-generated method stub var abilities = NativePatchBukkit.getAbilities(this.getUniqueId());
throw new UnsupportedOperationException("Unimplemented method 'setFlying'"); abilities.withFlying(value);
NativePatchBukkit.setAbilities(this.getUniqueId(), abilities);
} }
@Override @Override

View File

@ -5,8 +5,9 @@ use std::{
use anyhow::Result; use anyhow::Result;
use j4rs::{InvocationArg, Jvm}; use j4rs::{InvocationArg, Jvm};
use pumpkin::plugin::Context; use pumpkin::{entity::player::Abilities, plugin::Context};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use tokio::sync::MutexGuard;
static CALLBACK_CONTEXT: OnceLock<CallbackContext> = OnceLock::new(); static CALLBACK_CONTEXT: OnceLock<CallbackContext> = OnceLock::new();
@ -33,6 +34,8 @@ pub fn init_callback_context(
pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> { pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
let send_message_addr = rust_send_message as *const () as i64; let send_message_addr = rust_send_message as *const () as i64;
let register_event_addr = rust_register_event as *const () as i64; let register_event_addr = rust_register_event as *const () as i64;
let get_abilities_addr = rust_get_abilities as *const () as i64;
let set_abilities_addr = rust_set_abilities as *const () as i64;
jvm.invoke_static( jvm.invoke_static(
"org.patchbukkit.bridge.NativePatchBukkit", "org.patchbukkit.bridge.NativePatchBukkit",
@ -40,6 +43,8 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
&[ &[
InvocationArg::try_from(send_message_addr)?.into_primitive()?, InvocationArg::try_from(send_message_addr)?.into_primitive()?,
InvocationArg::try_from(register_event_addr)?.into_primitive()?, InvocationArg::try_from(register_event_addr)?.into_primitive()?,
InvocationArg::try_from(get_abilities_addr)?.into_primitive()?,
InvocationArg::try_from(set_abilities_addr)?.into_primitive()?,
], ],
)?; )?;
@ -66,6 +71,84 @@ pub extern "C" fn rust_send_message(uuid_ptr: *const c_char, message_ptr: *const
} }
} }
#[repr(C)]
pub struct AbilitiesFFI {
pub invulnerable: bool,
pub flying: bool,
pub allow_flying: bool,
pub creative: bool,
pub allow_modify_world: bool,
pub fly_speed: f32,
pub walk_speed: f32,
}
impl AbilitiesFFI {
fn new(abilities: MutexGuard<'_, Abilities>) -> Self {
Self {
invulnerable: abilities.invulnerable,
flying: abilities.flying,
allow_flying: abilities.allow_flying,
creative: abilities.creative,
allow_modify_world: abilities.allow_modify_world,
fly_speed: abilities.fly_speed,
walk_speed: abilities.walk_speed,
}
}
}
pub extern "C" fn rust_set_abilities(
uuid_ptr: *const c_char,
abilities: *mut AbilitiesFFI,
) -> bool {
let uuid_str = unsafe { CStr::from_ptr(uuid_ptr).to_string_lossy().into_owned() };
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 {
tokio::task::block_in_place(|| {
ctx.runtime.block_on(async {
let mut server_abilities = player.abilities.lock().await;
unsafe {
server_abilities.allow_flying = (*abilities).allow_flying;
server_abilities.allow_modify_world = (*abilities).allow_modify_world;
server_abilities.creative = (*abilities).creative;
server_abilities.fly_speed = (*abilities).fly_speed;
server_abilities.flying = (*abilities).flying;
server_abilities.invulnerable = (*abilities).invulnerable;
server_abilities.walk_speed = (*abilities).walk_speed;
}
})
});
return true;
}
}
false
}
pub extern "C" fn rust_get_abilities(uuid_ptr: *const c_char, out: *mut AbilitiesFFI) -> bool {
let uuid_str = unsafe { CStr::from_ptr(uuid_ptr).to_string_lossy().into_owned() };
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 abilities = tokio::task::block_in_place(|| {
ctx.runtime
.block_on(async { AbilitiesFFI::new(player.abilities.lock().await) })
});
unsafe {
*out = abilities;
}
return true;
}
}
false
}
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn rust_register_event( pub extern "C" fn rust_register_event(
listener_ptr: *const c_void, // opaque pointer to Java object listener_ptr: *const c_void, // opaque pointer to Java object

View File

@ -7,16 +7,11 @@ use std::{
use anyhow::Result; use anyhow::Result;
use j4rs::{Instance, InvocationArg, Jvm}; use j4rs::{Instance, InvocationArg, Jvm};
use pumpkin::{ use pumpkin::{
command::{ command::tree::{CommandTree, builder::literal},
tree::{
CommandTree,
builder::{argument, literal},
},
},
plugin::Context, plugin::Context,
}; };
use pumpkin_util::permission::{Permission, PermissionDefault}; use pumpkin_util::permission::{Permission, PermissionDefault};
use tokio::{runtime::Handle, sync::mpsc}; use tokio::sync::mpsc;
use crate::{ use crate::{
config::{ config::{