diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java index 61755af..14c24f1 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventFactory.java @@ -1,57 +1,89 @@ package org.patchbukkit.events; +import com.google.protobuf.InvalidProtocolBufferException; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import net.kyori.adventure.text.Component; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import patchbukkit.common.UUID; +import patchbukkit.events.Event; +import patchbukkit.events.FireEventResponse; +import patchbukkit.events.PlayerJoinEvent; -import java.util.UUID; import java.util.logging.Logger; public class PatchBukkitEventFactory { - private static final Logger LOGGER = Logger.getLogger("PatchBukkit"); @Nullable - public static PlayerJoinEvent createPlayerJoinEvent(@NotNull String playerUuid, @NotNull String joinMessage) { - Player player = getPlayer(playerUuid); - if (player == null) return null; - - Component joinMessageComponent = Component.translatable("multiplayer.player.joined", - Component.text(joinMessage)); - - return new PlayerJoinEvent(player, joinMessageComponent); + public static org.bukkit.event.Event createEventFromBytes(byte[] data) { + try { + Event event = Event.parseFrom(data); + return createEvent(event); + } catch (InvalidProtocolBufferException e) { + LOGGER.severe("Failed to parse Event: " + e.getMessage()); + return null; + } } @Nullable - public static PlayerQuitEvent createPlayerQuitEvent(@NotNull String playerUuid) { - Player player = getPlayer(playerUuid); - if (player == null) return null; + public static org.bukkit.event.Event createEvent(@NotNull Event event) { + Event.DataCase dataCase = event.getDataCase(); - Component quitMessage = Component.translatable("multiplayer.player.left", - Component.text(player.getName())); + return switch (dataCase) { + case PLAYER_JOIN -> { + PlayerJoinEvent joinEvent = event.getPlayerJoin(); + Player player = getPlayer(joinEvent.getPlayerUuid().getValue()); + if (player == null) yield null; - return new PlayerQuitEvent(player, quitMessage, PlayerQuitEvent.QuitReason.DISCONNECTED); + Component joinMessage = GsonComponentSerializer.gson().deserialize(joinEvent.getJoinMessage()); + yield new org.bukkit.event.player.PlayerJoinEvent(player, joinMessage); + } + case DATA_NOT_SET -> { + LOGGER.warning("EventFactory: Received Event with no data"); + yield null; + } + }; } - /** - * Check if an event implements Cancellable. - */ - public static boolean isCancellable(@NotNull Event event) { + @NotNull + public static byte[] toFireEventResponse(@NotNull org.bukkit.event.Event event) { + boolean cancelled = event instanceof org.bukkit.event.Cancellable c && c.isCancelled(); + + FireEventResponse.Builder builder = FireEventResponse.newBuilder() + .setCancelled(cancelled); + + Event.Builder eventBuilder = Event.newBuilder(); + + if (event instanceof org.bukkit.event.player.PlayerJoinEvent joinEvent) { + String joinMessage = GsonComponentSerializer.gson().serialize(joinEvent.joinMessage()); + + eventBuilder.setPlayerJoin( + PlayerJoinEvent.newBuilder() + .setPlayerUuid(UUID.newBuilder() + .setValue(joinEvent.getPlayer().getUniqueId().toString()) + .build()) + .setJoinMessage(joinMessage) + .build() + ); + } + + builder.setData(eventBuilder.build()); + + return builder.build().toByteArray(); + } + + public static boolean isCancellable(@NotNull org.bukkit.event.Event event) { return event instanceof org.bukkit.event.Cancellable; } - /** - * Look up a player by UUID string. Returns null with a warning if not found. - */ @Nullable private static Player getPlayer(@NotNull String uuidStr) { try { - UUID uuid = UUID.fromString(uuidStr); + java.util.UUID uuid = java.util.UUID.fromString(uuidStr); Player player = Bukkit.getServer().getPlayer(uuid); if (player == null) { LOGGER.warning("EventFactory: Player not found for UUID " + uuidStr); diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java index 4478f2a..ba94bfa 100644 --- a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java +++ b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventManager.java @@ -51,10 +51,12 @@ public class PatchBukkitEventManager { switch (event.getEventName()) { case "org.bukkit.event.player.PlayerJoinEvent": var castedEvent = (org.bukkit.event.player.PlayerJoinEvent) event; - request.setPlayerJoin( - PlayerJoinEvent.newBuilder() + request.setEvent( + patchbukkit.events.Event.newBuilder().setPlayerJoin( + PlayerJoinEvent.newBuilder() .setJoinMessage(castedEvent.joinMessage().toString()) - .setPlayerUuid(BridgeUtils.convertUuid(castedEvent.getPlayer().getUniqueId())) + .setPlayerUuid(BridgeUtils.convertUuid(castedEvent.getPlayer().getUniqueId())).build() + ).build() ); break; } diff --git a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventSerializer.java b/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventSerializer.java deleted file mode 100644 index e1c3fc6..0000000 --- a/java/patchbukkit/src/main/java/org/patchbukkit/events/PatchBukkitEventSerializer.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.patchbukkit.events; - -import org.bukkit.event.Event; -import org.bukkit.event.player.PlayerJoinEvent; - -import com.google.gson.JsonObject; - -public class PatchBukkitEventSerializer { - public static String serialize(Event event) { - JsonObject json = new JsonObject(); - - if (event instanceof PlayerJoinEvent joinEvent) { - var joinMessage = joinEvent.joinMessage(); - if (joinMessage != null) { - json.addProperty("playerUuid", joinEvent.getPlayer().getUniqueId().toString()); - json.addProperty("joinMessage", joinMessage.toString()); - } - } - - return json.toString(); - } -} diff --git a/proto/patchbukkit/events/event.proto b/proto/patchbukkit/events/event.proto index d6110e5..f678892 100644 --- a/proto/patchbukkit/events/event.proto +++ b/proto/patchbukkit/events/event.proto @@ -8,6 +8,12 @@ import "patchbukkit/events/player_events.proto"; option java_multiple_files = true; option java_package = "patchbukkit.events"; +message Event { + oneof data { + PlayerJoinEvent player_join = 1; + } +} + message RegisterEventRequest { string event_type = 1; string plugin_name = 2; @@ -16,11 +22,14 @@ message RegisterEventRequest { } message CallEventRequest { - oneof event_data { - PlayerJoinEvent player_join = 1; - } + Event event = 1; } message CallEventResponse { bool handled = 1; } + +message FireEventResponse { + bool cancelled = 1; + Event data = 2; +} diff --git a/rust/src/events/handler.rs b/rust/src/events/handler.rs index 6fa3738..b41e374 100644 --- a/rust/src/events/handler.rs +++ b/rust/src/events/handler.rs @@ -1,4 +1,5 @@ use std::marker::PhantomData; +use std::str::FromStr; use std::sync::Arc; use pumpkin::entity::player::Player; @@ -8,37 +9,62 @@ use pumpkin_api_macros::with_runtime; use tokio::sync::{mpsc, oneshot}; use crate::java::jvm::commands::JvmCommand; +use crate::proto::patchbukkit::common::Uuid; +use crate::proto::patchbukkit::events::event::Data; +use crate::proto::patchbukkit::events::{Event, PlayerJoinEvent}; -#[derive(Clone)] -pub enum PatchBukkitEvent { - PlayerJoinEvent { - server: Arc, - player: Arc, - join_message: String, - }, +pub struct EventContext { + pub server: Arc, + pub player: Option>, } -pub trait IntoEventData { - fn get_patch_bukkit_event(&self, server: Arc) -> PatchBukkitEvent; +pub struct JvmEventPayload { + pub event: Event, + pub context: EventContext, } -impl IntoEventData for pumpkin::plugin::player::player_join::PlayerJoinEvent { - fn get_patch_bukkit_event(&self, server: Arc) -> PatchBukkitEvent { - PatchBukkitEvent::PlayerJoinEvent { - server, - player: self.player.clone(), - join_message: self.join_message.clone().get_text(), +pub trait PatchBukkitEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload; + fn apply_modifications(&mut self, server: &Arc, data: Data) -> Option<()>; +} + +impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent { + fn to_payload(&self, server: Arc) -> JvmEventPayload { + JvmEventPayload { + event: Event { + data: Some(Data::PlayerJoin(PlayerJoinEvent { + player_uuid: Some(Uuid { + value: self.player.gameprofile.id.to_string(), + }), + join_message: serde_json::to_string(&self.join_message).unwrap(), + })), + }, + context: EventContext { + server, + player: Some(self.player.clone()), + }, } } + + fn apply_modifications(&mut self, server: &Arc, data: Data) -> Option<()> { + match data { + Data::PlayerJoin(event) => { + self.join_message = serde_json::from_str(&event.join_message).ok()?; + server.get_player_by_uuid(uuid::Uuid::from_str(&event.player_uuid?.value).ok()?)?; + } + } + + Some(()) + } } -pub struct PatchBukkitEventHandler { +pub struct PatchBukkitEventHandler { plugin_name: String, command_tx: mpsc::Sender, _phantom: PhantomData, } -impl PatchBukkitEventHandler { +impl PatchBukkitEventHandler { #[must_use] pub const fn new(plugin_name: String, command_tx: mpsc::Sender) -> Self { Self { @@ -52,7 +78,7 @@ impl PatchBukkitEventHandler { #[with_runtime(global)] impl EventHandler for PatchBukkitEventHandler where - E: IntoEventData + Payload + Cancellable + 'static, + E: PatchBukkitEvent + Payload + Cancellable + 'static, { fn handle_blocking<'a>( &'a self, @@ -65,7 +91,7 @@ where let (tx, rx) = oneshot::channel(); if let Err(e) = command_tx .send(JvmCommand::FireEvent { - patchbukkit_event: event.get_patch_bukkit_event(server.clone()), + payload: event.to_payload(server.clone()), respond_to: tx, plugin: self.plugin_name.clone(), }) @@ -76,11 +102,9 @@ where } match rx.await { - Ok(cancelled) => { - if cancelled { - log::debug!("Event was cancelled by a Java plugin"); - event.set_cancelled(true); - } + Ok(response) => { + event.set_cancelled(response.cancelled); + event.apply_modifications(server, response.data.unwrap().data.unwrap()); } Err(_) => { log::warn!("JVM worker dropped response channel for event"); diff --git a/rust/src/java/jvm/commands.rs b/rust/src/java/jvm/commands.rs index 8b080b9..a8301f6 100644 --- a/rust/src/java/jvm/commands.rs +++ b/rust/src/java/jvm/commands.rs @@ -6,7 +6,10 @@ use pumpkin_protocol::java::client::play::CommandSuggestion; use tokio::sync::{mpsc, oneshot}; use uuid::Uuid; -use crate::{commands::SimpleCommandSender, events::handler::PatchBukkitEvent}; +use crate::{ + commands::SimpleCommandSender, events::handler::JvmEventPayload, + proto::patchbukkit::events::FireEventResponse, +}; pub enum LoadPluginResult { SuccessfullyLoadedSpigot, @@ -43,9 +46,9 @@ pub enum JvmCommand { respond_to: oneshot::Sender>, }, FireEvent { - patchbukkit_event: PatchBukkitEvent, + payload: JvmEventPayload, plugin: String, - respond_to: oneshot::Sender, // true = cancelled + respond_to: oneshot::Sender, // true = cancelled }, TriggerCommand { full_command: String, diff --git a/rust/src/java/jvm/worker.rs b/rust/src/java/jvm/worker.rs index ba753da..2878519 100644 --- a/rust/src/java/jvm/worker.rs +++ b/rust/src/java/jvm/worker.rs @@ -4,13 +4,16 @@ use j4rs::{InvocationArg, Jvm, JvmBuilder}; use pumpkin::plugin::Context; use tokio::sync::mpsc; -use crate::java::{ - jar::read_configs_from_jar, - jvm::commands::{JvmCommand, LoadPluginResult}, - native_callbacks::{init_callback_context, initialize_callbacks}, - plugin::{ - command_manager::CommandManager, event_manager::EventManager, manager::PluginManager, +use crate::{ + java::{ + jar::read_configs_from_jar, + jvm::commands::{JvmCommand, LoadPluginResult}, + native_callbacks::{init_callback_context, initialize_callbacks}, + plugin::{ + command_manager::CommandManager, event_manager::EventManager, manager::PluginManager, + }, }, + proto::patchbukkit::events::FireEventResponse, }; pub struct JvmWorker { @@ -138,24 +141,24 @@ impl JvmWorker { JvmCommand::FireEvent { respond_to, plugin, - patchbukkit_event, + payload, } => { let jvm = match self.jvm { Some(ref jvm) => jvm, None => &Jvm::attach_thread().unwrap(), }; - let cancelled = - match self - .event_manager - .fire_event(jvm, patchbukkit_event, plugin) - { - Ok(c) => c, - Err(e) => { - log::error!("Failed to fire event: {e}"); - false + let original_event = payload.event.clone(); + let cancelled = match self.event_manager.fire_event(jvm, payload, plugin) { + Ok(c) => c, + Err(e) => { + log::error!("Failed to fire event: {e}"); + FireEventResponse { + cancelled: false, + data: Some(original_event), } - }; + } + }; let _ = respond_to.send(cancelled); } diff --git a/rust/src/java/native_callbacks/events.rs b/rust/src/java/native_callbacks/events.rs index 4b085a4..3b5fa28 100644 --- a/rust/src/java/native_callbacks/events.rs +++ b/rust/src/java/native_callbacks/events.rs @@ -6,7 +6,7 @@ use pumpkin_util::text::TextComponent; use crate::events::handler::PatchBukkitEventHandler; use crate::java::native_callbacks::CALLBACK_CONTEXT; -use crate::proto::patchbukkit::events::call_event_request::EventData; +use crate::proto::patchbukkit::events::event::Data; use crate::proto::patchbukkit::events::{ CallEventRequest, CallEventResponse, RegisterEventRequest, }; @@ -65,15 +65,15 @@ pub fn ffi_native_bridge_register_event_impl(request: RegisterEventRequest) -> O pub fn ffi_native_bridge_call_event_impl(request: CallEventRequest) -> Option { let ctx = CALLBACK_CONTEXT.get()?; - let event_data = request.event_data?; - log::debug!("Java calling event {:?}", event_data); + let event = request.event?; + log::debug!("Java calling event {:?}", event); let context = ctx.plugin_context.clone(); let handled = tokio::task::block_in_place(|| { ctx.runtime.block_on(async { - match event_data { - EventData::PlayerJoin(player_join_event_data) => { + match event.data? { + Data::PlayerJoin(player_join_event_data) => { let uuid = uuid::Uuid::parse_str(&player_join_event_data.player_uuid?.value).ok()?; let player = context.server.get_player_by_uuid(uuid)?; diff --git a/rust/src/java/plugin/event_manager.rs b/rust/src/java/plugin/event_manager.rs index f2d56f7..f295f98 100644 --- a/rust/src/java/plugin/event_manager.rs +++ b/rust/src/java/plugin/event_manager.rs @@ -2,9 +2,13 @@ use std::sync::Arc; use anyhow::Result; use j4rs::{Instance, InvocationArg, Jvm}; +use prost::Message; use pumpkin::{entity::player::Player, server::Server}; -use crate::events::handler::PatchBukkitEvent; +use crate::{ + events::handler::JvmEventPayload, + proto::patchbukkit::events::{FireEventResponse, event::Data}, +}; pub struct EventManager {} @@ -23,61 +27,71 @@ impl EventManager { pub fn fire_event( &self, jvm: &Jvm, - event: PatchBukkitEvent, + payload: JvmEventPayload, plugin_name: String, - ) -> Result { + ) -> Result { let server = jvm.invoke_static("org.bukkit.Bukkit", "getServer", InvocationArg::empty())?; let patch_server = jvm.cast(&server, "org.patchbukkit.PatchBukkitServer")?; let event_manager = jvm.invoke(&patch_server, "getEventManager", InvocationArg::empty())?; - let j_event = match event { - PatchBukkitEvent::PlayerJoinEvent { - server, - player, - join_message, - } => { - Self::register_player(jvm, &patch_server, &player, &server)?; - jvm.invoke_static( - "org.patchbukkit.events.PatchBukkitEventFactory", - "createPlayerJoinEvent", - &[ - InvocationArg::try_from(player.gameprofile.id.to_string())?, - InvocationArg::try_from(join_message)?, - ], - )? - } - }; + if let Some(ref event) = payload.event.data + && matches!(event, Data::PlayerJoin(_)) + && let Some(ref player) = payload.context.player + { + Self::register_player(jvm, &patch_server, player, &payload.context.server)?; + } + + let bytes = payload.event.encode_to_vec(); + let j_bytes = jvm.create_java_array( + "byte", + &bytes + .iter() + .map(|&b| { + InvocationArg::try_from(b as i8) + .unwrap() + .into_primitive() + .unwrap() + }) + .collect::>(), + )?; + + let j_event = jvm.invoke_static( + "org.patchbukkit.events.PatchBukkitEventFactory", + "createEventFromBytes", + &[InvocationArg::from(j_bytes)], + )?; + + let is_null: bool = jvm.to_rust(jvm.invoke_static( + "java.util.Objects", + "isNull", + &[InvocationArg::from(jvm.clone_instance(&j_event)?)], + )?)?; + + if is_null { + return Err(anyhow::anyhow!( + "Failed to create event - factory returned null" + )); + } - let j_event_for_fire = jvm.clone_instance(&j_event)?; jvm.invoke( &event_manager, "fireEvent", &[ - InvocationArg::from(j_event_for_fire), + InvocationArg::from(jvm.clone_instance(&j_event)?), InvocationArg::try_from(plugin_name)?, ], )?; - let is_cancellable: bool = jvm.to_rust(jvm.invoke_static( + let response_bytes: Vec = jvm.to_rust(jvm.invoke_static( "org.patchbukkit.events.PatchBukkitEventFactory", - "isCancellable", - &[InvocationArg::from(jvm.clone_instance(&j_event)?)], + "toFireEventResponse", + &[InvocationArg::from(j_event)], )?)?; - let cancelled = if is_cancellable { - match jvm.invoke( - &jvm.clone_instance(&j_event)?, - "isCancelled", - InvocationArg::empty(), - ) { - Ok(instance) => jvm.to_rust::(instance).unwrap_or(false), - Err(_) => false, - } - } else { - false - }; + let response_bytes: Vec = response_bytes.iter().map(|&b| b as u8).collect(); + let response = FireEventResponse::decode(response_bytes.as_slice())?; - Ok(cancelled) + Ok(response) } pub fn register_player(