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.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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::{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user