Implement get and set abilities
This commit is contained in:
parent
20c823eb76
commit
b4521e1c8e
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<CallbackContext> = 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
|
||||
|
||||
@ -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::{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user