diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java index b788126..e4ffb61 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/bridge/NativePatchBukkit.java @@ -2,7 +2,9 @@ package org.patchbukkit.bridge; 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; @@ -11,9 +13,83 @@ public class NativePatchBukkit { private static MethodHandle sendMessageNative; private static MethodHandle registerEventNative; + private static MethodHandle getAbilitiesNative; + private static MethodHandle setAbilitiesNative; - // Called from Rust during initialization - public static void initCallbacks(long sendMessageAddr, long registerEventAddr) { + // 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") + ); + + // 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) sendMessageNative = LINKER.downcallHandle( MemorySegment.ofAddress(sendMessageAddr), @@ -25,31 +101,108 @@ public class NativePatchBukkit { MemorySegment.ofAddress(registerEventAddr), 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) { try (Arena arena = Arena.ofConfined()) { MemorySegment uuidStr = arena.allocateFrom(uuid.toString()); MemorySegment msgStr = arena.allocateFrom(message); - sendMessageNative.invokeExact(uuidStr, msgStr); } catch (Throwable 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) { // For objects, you have options: // 1. Pass identifying info (strings, IDs) instead of object refs // 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 - // 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()); + registerEventNative.invokeExact(listenerClass, pluginName); } catch (Throwable 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); + } + } + } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java index 477642b..e1d25f6 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/entity/PatchBukkitPlayer.java @@ -1176,14 +1176,14 @@ public class PatchBukkitPlayer @Override public boolean getAllowFlight() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'getAllowFlight'"); + return NativePatchBukkit.getAbilities(this.uuid).allowFlying(); } @Override public void setAllowFlight(boolean flight) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setAllowFlight'"); + var abilities = NativePatchBukkit.getAbilities(this.uuid); + abilities.withAllowFlying(flight); + NativePatchBukkit.setAbilities(this.uuid, abilities); } @Override @@ -1260,8 +1260,9 @@ public class PatchBukkitPlayer @Override public void setFlying(boolean value) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'setFlying'"); + var abilities = NativePatchBukkit.getAbilities(this.getUniqueId()); + abilities.withFlying(value); + NativePatchBukkit.setAbilities(this.getUniqueId(), abilities); } @Override diff --git a/rust/src/java/native_callbacks/mod.rs b/rust/src/java/native_callbacks/mod.rs index 9f9e84b..9c0fd16 100644 --- a/rust/src/java/native_callbacks/mod.rs +++ b/rust/src/java/native_callbacks/mod.rs @@ -5,8 +5,9 @@ use std::{ use anyhow::Result; use j4rs::{InvocationArg, Jvm}; -use pumpkin::plugin::Context; +use pumpkin::{entity::player::Abilities, plugin::Context}; use pumpkin_util::text::TextComponent; +use tokio::sync::MutexGuard; static CALLBACK_CONTEXT: OnceLock = OnceLock::new(); @@ -33,6 +34,8 @@ pub fn init_callback_context( pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> { let send_message_addr = rust_send_message 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( "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(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)] pub extern "C" fn rust_register_event( listener_ptr: *const c_void, // opaque pointer to Java object diff --git a/rust/src/plugin/manager.rs b/rust/src/plugin/manager.rs index 2b4d2d0..0b69a08 100644 --- a/rust/src/plugin/manager.rs +++ b/rust/src/plugin/manager.rs @@ -7,16 +7,11 @@ use std::{ use anyhow::Result; use j4rs::{Instance, InvocationArg, Jvm}; use pumpkin::{ - command::{ - tree::{ - CommandTree, - builder::{argument, literal}, - }, - }, + command::tree::{CommandTree, builder::literal}, plugin::Context, }; use pumpkin_util::permission::{Permission, PermissionDefault}; -use tokio::{runtime::Handle, sync::mpsc}; +use tokio::sync::mpsc; use crate::{ config::{