Let events modify
This commit is contained in:
parent
a1abcfa8fd
commit
3f4db9f5b4
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<Server>,
|
||||
player: Arc<Player>,
|
||||
join_message: String,
|
||||
},
|
||||
pub struct EventContext {
|
||||
pub server: Arc<Server>,
|
||||
pub player: Option<Arc<Player>>,
|
||||
}
|
||||
|
||||
pub trait IntoEventData {
|
||||
fn get_patch_bukkit_event(&self, server: Arc<Server>) -> 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<Server>) -> 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<Server>) -> JvmEventPayload;
|
||||
fn apply_modifications(&mut self, server: &Arc<Server>, data: Data) -> Option<()>;
|
||||
}
|
||||
|
||||
impl PatchBukkitEvent for pumpkin::plugin::player::player_join::PlayerJoinEvent {
|
||||
fn to_payload(&self, server: Arc<Server>) -> 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<Server>, 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<E: IntoEventData> {
|
||||
pub struct PatchBukkitEventHandler<E: PatchBukkitEvent> {
|
||||
plugin_name: String,
|
||||
command_tx: mpsc::Sender<JvmCommand>,
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E: IntoEventData> PatchBukkitEventHandler<E> {
|
||||
impl<E: PatchBukkitEvent> PatchBukkitEventHandler<E> {
|
||||
#[must_use]
|
||||
pub const fn new(plugin_name: String, command_tx: mpsc::Sender<JvmCommand>) -> Self {
|
||||
Self {
|
||||
@ -52,7 +78,7 @@ impl<E: IntoEventData> PatchBukkitEventHandler<E> {
|
||||
#[with_runtime(global)]
|
||||
impl<E> EventHandler<E> for PatchBukkitEventHandler<E>
|
||||
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");
|
||||
|
||||
@ -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<Result<()>>,
|
||||
},
|
||||
FireEvent {
|
||||
patchbukkit_event: PatchBukkitEvent,
|
||||
payload: JvmEventPayload,
|
||||
plugin: String,
|
||||
respond_to: oneshot::Sender<bool>, // true = cancelled
|
||||
respond_to: oneshot::Sender<FireEventResponse>, // true = cancelled
|
||||
},
|
||||
TriggerCommand {
|
||||
full_command: String,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<CallEventResponse> {
|
||||
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)?;
|
||||
|
||||
@ -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<bool> {
|
||||
) -> Result<FireEventResponse> {
|
||||
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::<Vec<_>>(),
|
||||
)?;
|
||||
|
||||
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<i8> = 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::<bool>(instance).unwrap_or(false),
|
||||
Err(_) => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let response_bytes: Vec<u8> = 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(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user