chore: nicer event firing

This commit is contained in:
Alexander Medvedev
2026-08-03 21:16:07 +02:00
parent 5eaf550a4c
commit e1117ce4a6
25 changed files with 437 additions and 400 deletions

View File

@@ -1,12 +1,12 @@
use heck::ToShoutySnakeCase;
use proc_macro::TokenStream;
use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
use proc_macro_error2::{abort, abort_call_site};
use pumpkin_data::tag::{RegistryKey, get_tag_ids};
use pumpkin_data::{Block, BlockId};
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{self, Attribute, DeriveInput, LitStr, Type, parse_quote};
use syn::{Expr, Field, Fields, ItemStruct, Stmt, parse_macro_input};
use syn::{Block as SynBlock, Expr, Field, Fields, ItemStruct, Stmt, parse_macro_input};
/// Derives the `Payload` trait for an event struct, enabling it to be used in the plugin system.
///
@@ -89,87 +89,97 @@ pub fn cancellable(_args: TokenStream, input: TokenStream) -> TokenStream {
.into()
}
/// Sends a cancellable event asynchronously.
/// Sends a cancellable event through the plugin manager.
///
/// # Arguments
/// - `input` The input `TokenStream` representing the labelled blocks and event expressions.
#[proc_macro_error]
/// # Syntax
/// ```ignore
/// send_cancellable! {{
/// <server_expr>;
/// <event_expr>;
/// 'after: { <after_stmts> }
/// 'cancelled: { <cancelled_stmts> }
/// }}
/// ```
#[proc_macro]
pub fn send_cancellable(input: TokenStream) -> TokenStream {
let block = parse_macro_input!(input as syn::Block);
let input = parse_macro_input!(input as SynBlock);
let mut stmts_iter = input.stmts.into_iter();
let Some(Stmt::Expr(server_stmt, _)) = stmts_iter.next() else {
abort_call_site!("expected server expression as first statement")
};
let Some(Stmt::Expr(event_stmt, _)) = stmts_iter.next() else {
abort_call_site!("expected event expression as second statement")
};
let event_expr = if let Expr::Reference(syn::ExprReference {
expr,
mutability: Some(_),
..
}) = event_stmt
{
*expr
} else {
event_stmt
};
let mut server_expr = None;
let mut event_expr = None;
let mut after_block = None;
let mut cancelled_block = None;
for stmt in block.stmts {
match stmt {
Stmt::Expr(expr, _) => {
// Check if it is a labelled block first.
let mut is_special_block = false;
if let Expr::Block(ref b) = expr
&& let Some(ref label) = b.label
{
let label_name = label.name.ident.to_string();
if label_name == "after" {
after_block = Some(b.clone()); // Clone strictly necessary here as we split AST.
is_special_block = true;
} else if label_name == "cancelled" {
cancelled_block = Some(b.clone());
is_special_block = true;
}
}
// If it wasn't a special block, it must be the event expression.
if !is_special_block {
if server_expr.is_none() {
server_expr = Some(expr);
} else if event_expr.is_none() {
event_expr = Some(expr);
} else {
abort!(
expr.span(),
"Multiple event expressions found. Only one event expression allowed."
);
}
}
for stmt in stmts_iter {
if let Stmt::Expr(Expr::Block(b), _) = stmt
&& let Some(ref label) = b.label
{
if label.name.ident == "after" {
after_block = Some(b.block);
} else if label.name.ident == "cancelled" {
cancelled_block = Some(b.block);
}
// Abort on other statements (like `let x = ...`) if strictness is desired.
_ => abort!(
stmt.span(),
"Only event expressions and labeled blocks allowed in `send_cancellable!`"
),
}
}
let Some(server) = server_expr else {
abort_call_site!("Server expression must be specified");
};
let Some(event) = event_expr else {
abort_call_site!("Event expression must be specified");
};
// Construct the if/else logic.
let logic = match (after_block, cancelled_block) {
let execution = match (after_block, cancelled_block) {
(Some(after), Some(cancelled)) => quote! {
if !event.cancelled { #after } else { #cancelled }
if !is_cancelled {
#after
} else {
#cancelled
}
},
(Some(after), None) => quote! {
if !event.cancelled { #after }
if !is_cancelled {
#after
}
},
(None, Some(cancelled)) => quote! {
if event.cancelled { #cancelled }
if is_cancelled {
#cancelled
}
},
(None, None) => quote! {},
};
quote! {
let event = #server.plugin_manager.fire(#event).await;
#logic
}
.into()
let expanded = quote! {
{
let mut event = #event_expr;
let server_ref: &std::sync::Arc<crate::server::Server> = {
use std::borrow::Borrow;
(#server_stmt).borrow()
};
server_ref.plugin_manager.fire(server_ref, &mut event).await;
let is_cancelled = {
use crate::plugin::Cancellable;
event.cancelled()
};
#execution
}
};
expanded.into()
}
/// Attaches a fixed packet ID to a struct implementing `Packet`.

View File

@@ -157,9 +157,6 @@ impl<R: AsyncRead + Unpin> TCPNetworkDecoder<R> {
DecompressionReader::None(bounded_reader)
};
// TODO: Serde is sync so we need to write to a buffer here :(
// Is there a way to deserialize in an asynchronous manner?
let packet_id = VarInt::decode_async(&mut reader)
.await
.map_err(|_| PacketDecodeError::DecodeID)?

View File

@@ -17,25 +17,22 @@ impl Cylindrical {
}
}
pub fn for_each_changed_chunk(
old_cylindrical: Self,
new_cylindrical: Self,
newly_included: &mut Vec<Vector2<i32>>,
just_removed: &mut Vec<Vector2<i32>>,
pub fn changed_chunks(
old: Self,
new: Self,
) -> (
impl Iterator<Item = Vector2<i32>>,
impl Iterator<Item = Vector2<i32>>,
) {
for new_cylindrical_chunk in new_cylindrical.all_chunks_within() {
if !old_cylindrical.is_within_distance(new_cylindrical_chunk.x, new_cylindrical_chunk.y)
{
newly_included.push(new_cylindrical_chunk);
}
}
let loading = new
.all_chunks_within()
.filter(move |c| !old.is_within_distance(c.x, c.y));
for old_cylindrical_chunk in old_cylindrical.all_chunks_within() {
if !new_cylindrical.is_within_distance(old_cylindrical_chunk.x, old_cylindrical_chunk.y)
{
just_removed.push(old_cylindrical_chunk);
}
}
let unloading = old
.all_chunks_within()
.filter(move |c| !new.is_within_distance(c.x, c.y));
(loading, unloading)
}
#[allow(dead_code)]

View File

@@ -56,7 +56,7 @@ trait CropBlockBase: PlantBlockBase {
if rand::rng().random_range(0..=(25.0 / f).floor() as i64) == 0 {
let mut new_state_id = self.state_with_age(block, state, age + 1);
if let Some(server) = world.server.upgrade() {
let event = BlockGrowEvent::new(
let mut event = BlockGrowEvent::new(
world.clone(),
block,
state,
@@ -64,7 +64,7 @@ trait CropBlockBase: PlantBlockBase {
new_state_id,
*pos,
);
let event = server.plugin_manager.fire(event).await;
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
return;
}

View File

@@ -64,14 +64,14 @@ pub(crate) trait PressurePlate {
let has_output = calc_output > 0;
if calc_output != output {
let next_output = if let Some(server) = world.server.upgrade() {
let event = crate::plugin::block::block_redstone::BlockRedstoneEvent::new(
let mut event = crate::plugin::block::block_redstone::BlockRedstoneEvent::new(
world.clone(),
state.id,
*pos,
i32::from(output),
i32::from(calc_output),
);
let event = server.plugin_manager.fire(event).await;
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
return;
}

View File

@@ -460,7 +460,7 @@ impl BlockRegistry {
&self,
player: &Arc<Player>,
placed_block: &'static Block,
server: &Server,
server: &Arc<Server>,
use_item_on: &SUseItemOn,
location: BlockPos,
face: BlockDirection,
@@ -605,16 +605,16 @@ impl BlockRegistry {
}
}
let event = crate::plugin::block::block_place::BlockPlaceEvent::new(
let mut event = crate::plugin::block::block_place::BlockPlaceEvent::new(
player.clone(),
placed_block,
clicked_block,
final_block_pos,
true,
);
let event = server
server
.plugin_manager
.fire::<crate::plugin::block::block_place::BlockPlaceEvent>(event)
.fire::<crate::plugin::block::block_place::BlockPlaceEvent>(server, &mut event)
.await;
if event.cancelled {
return Ok(None);

View File

@@ -28,7 +28,16 @@ impl CommandExecutor for Executor {
return Err(InvalidConsumption(Some(ARG_MESSAGE.into())));
};
server
let Some(server_arc) = sender
.world_or_first(server)
.and_then(|w| w.server.upgrade())
else {
return Err(CommandError::CommandFailed(TextComponent::text(
"Failed to get server instance",
)));
};
server_arc
.broadcast_message(
&TextComponent::text(msg.clone()),
&TextComponent::text(format!("{sender}")),

View File

@@ -90,9 +90,18 @@ impl CommandExecutor for LoadExecutor {
))));
}
let Some(server_arc) = sender
.world_or_first(server)
.and_then(|w| w.server.upgrade())
else {
return Err(CommandError::CommandFailed(TextComponent::text(
"Failed to get server instance",
)));
};
let result = server
.plugin_manager
.try_load_plugin(Path::new(plugin_name))
.try_load_plugin(&server_arc, Path::new(plugin_name))
.await;
match result {
@@ -171,7 +180,16 @@ impl CommandExecutor for HotReloadExecutor {
let enabled = self.0;
if enabled {
if let Err(e) = server.plugin_manager.start_watcher().await {
let Some(server_arc) = sender
.world_or_first(server)
.and_then(|w| w.server.upgrade())
else {
return Err(CommandError::CommandFailed(TextComponent::text(
"Failed to get server instance",
)));
};
if let Err(e) = server.plugin_manager.start_watcher(&server_arc).await {
return Err(CommandError::CommandFailed(TextComponent::text(format!(
"Failed to start plugin watcher: {e}"
))));

View File

@@ -28,7 +28,16 @@ impl CommandExecutor for Executor {
return Err(InvalidConsumption(Some(ARG_MESSAGE.into())));
};
server
let Some(server_arc) = sender
.world_or_first(server)
.and_then(|w| w.server.upgrade())
else {
return Err(CommandError::CommandFailed(TextComponent::text(
"Failed to get server instance",
)));
};
server_arc
.broadcast_message(
&TextComponent::text(msg.clone()),
&TextComponent::text(format!("{sender}")),

View File

@@ -126,7 +126,7 @@ async fn setworldspawn(
let previous_pitch = current_info.spawn_pitch;
let mut new_yaw = yaw;
let mut new_pitch = pitch;
let event = SpawnChangeEvent::new(
let mut event = SpawnChangeEvent::new(
world.clone(),
previous_position,
previous_yaw,
@@ -135,7 +135,12 @@ async fn setworldspawn(
new_yaw,
new_pitch,
);
let event = server.plugin_manager.fire(event).await;
if let Some(server_arc) = world.server.upgrade() {
server_arc
.plugin_manager
.fire(&server_arc, &mut event)
.await;
}
new_position = event.new_position;
new_yaw = event.new_yaw;
new_pitch = event.new_pitch;

View File

@@ -436,7 +436,8 @@ impl CommandDispatcher {
return Err(CommandFailed(TextComponent::text("Empty Command")));
}
let key = args.remove(0).value;
Ok((key, args.into_iter().rev().collect()))
args.reverse();
Ok((key, args))
}
/// Execute a command using its corresponding [`CommandTree`].
@@ -448,9 +449,9 @@ impl CommandDispatcher {
) -> Result<(), CommandError> {
let (key, raw_args) = Self::split_parts(cmd)?;
if !self.commands.contains_key(key) {
return Err(SyntaxError(unknown_command_syntax_error(cmd, 0)));
}
let tree = self
.get_tree(key)
.map_err(|_| SyntaxError(unknown_command_syntax_error(cmd, 0)))?;
let Some(permission) = self.permissions.get(key) else {
return Err(CommandFailed(TextComponent::text(
@@ -462,8 +463,6 @@ impl CommandDispatcher {
return Err(PermissionDenied);
}
let tree = self.get_tree(key)?;
let mut path_failures = Vec::new();
// try paths until fitting path is found

View File

@@ -2174,8 +2174,9 @@ impl Player {
payload: Bytes,
) -> bool {
if let Some(server) = self.world().server.upgrade() {
let event = PacketSentEvent::new(self.clone(), packet_id, payload, Arc::new(packet));
let event = server.plugin_manager.fire(event).await;
let mut event =
PacketSentEvent::new(self.clone(), packet_id, payload, Arc::new(packet));
server.plugin_manager.fire(&server, &mut event).await;
return event.cancelled;
}
false
@@ -2186,8 +2187,9 @@ impl Player {
// This is a dummy object to satisfy the non-optional requirement in WIT
// In the future we should make all packets 'static or have a way to represent raw packets in WIT
struct RawPacket;
let event = PacketSentEvent::new(self.clone(), packet_id, payload, Arc::new(RawPacket));
let event = server.plugin_manager.fire(event).await;
let mut event =
PacketSentEvent::new(self.clone(), packet_id, payload, Arc::new(RawPacket));
server.plugin_manager.fire(&server, &mut event).await;
return event.cancelled;
}
false
@@ -3501,8 +3503,8 @@ impl Player {
/// Add experience points to the player.
pub async fn add_experience_points(self: &Arc<Self>, mut added_points: i32) {
if let Some(server) = self.world().server.upgrade() {
let event = PlayerExpChangeEvent::new(self.clone(), added_points);
let event = server.plugin_manager.fire(event).await;
let mut event = PlayerExpChangeEvent::new(self.clone(), added_points);
server.plugin_manager.fire(&server, &mut event).await;
added_points = event.amount;
}
@@ -3637,15 +3639,12 @@ impl Player {
};
if let Some(server) = self.living_entity.entity.world.load().server.upgrade() {
server
.plugin_manager
.fire(
crate::plugin::api::events::player::inventory_close::InventoryCloseEvent::new(
self,
window_type,
),
)
.await;
let mut event =
crate::plugin::api::events::player::inventory_close::InventoryCloseEvent::new(
self,
window_type,
);
server.plugin_manager.fire(&server, &mut event).await;
}
let player_screen_handler: Arc<Mutex<dyn ScreenHandler>> =
@@ -3829,7 +3828,7 @@ impl Player {
}
#[allow(clippy::too_many_lines)]
pub async fn on_slot_click(self: &Arc<Self>, packet: SClickSlot, server: &Server) {
pub async fn on_slot_click(self: &Arc<Self>, packet: SClickSlot, server: &Arc<Server>) {
self.update_last_action_time();
let screen_handler_arc = self.current_screen_handler.lock().await.clone();
let mut screen_handler = screen_handler_arc.lock().await;
@@ -4060,14 +4059,13 @@ impl Player {
.await;
drop(perm_manager);
let event = server
.plugin_manager
.fire(PlayerPermissionCheckEvent::new(
self.clone(),
node.to_string(),
result,
))
.await;
let mut event = PlayerPermissionCheckEvent::new(self.clone(), node.to_string(), result);
if let Some(server_arc) = self.world().server.upgrade() {
server_arc
.plugin_manager
.fire(&server_arc, &mut event)
.await;
}
event.result
}

View File

@@ -129,14 +129,14 @@ impl EntityBase for EggEntity {
&& let Some(player) = world.get_player_by_id(owner_id)
&& let Some(server) = world.server.upgrade()
{
let event = PlayerEggThrowEvent::new(
let mut event = PlayerEggThrowEvent::new(
player,
self.get_entity().entity_uuid,
hatching,
to_spawn as u8,
hatching_type,
);
let event = server.plugin_manager.fire(event).await;
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
hatching = false;
} else {

View File

@@ -1,6 +1,9 @@
// Not warn event sending macros
#![allow(unused_labels)]
#[macro_use]
extern crate pumpkin_macros;
use crate::crash::CrashReport;
use crate::data::VanillaData;
use crate::logging::{GzipRollingLogger, PumpkinCommandCompleter, ReadlineLogWrapper};
@@ -8,11 +11,10 @@ use crate::net::bedrock::BedrockClient;
use crate::net::java::JavaClient;
use crate::net::{ClientPlatform, DisconnectReason, PacketHandlerResult};
use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer};
use crate::plugin::server::server_command::ServerCommandEvent;
use crate::server::{Server, ticker::Ticker};
use plugin::server::server_command::ServerCommandEvent;
use plugin::server::server_load::{LoadType, ServerLoadEvent};
use pumpkin_config::{AdvancedConfiguration, BasicConfiguration};
use pumpkin_macros::send_cancellable;
use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::{Color, NamedColor};
use rustyline::Editor;
@@ -310,15 +312,7 @@ impl PumpkinServer {
}
pub async fn init_plugins(&self) -> std::time::Duration {
self.server
.plugin_manager
.set_self_ref(self.server.plugin_manager.clone())
.await;
self.server
.plugin_manager
.set_server(self.server.clone())
.await;
match self.server.plugin_manager.load_plugins().await {
match self.server.plugin_manager.load_plugins(&self.server).await {
Ok(duration) => duration,
Err(err) => {
error!("{err}");
@@ -355,10 +349,9 @@ impl PumpkinServer {
let mut master_client_id: u64 = 0;
let bedrock_clients = Arc::new(Mutex::new(HashMap::new()));
let _ = self
.server
self.server
.plugin_manager
.fire(ServerLoadEvent::new(LoadType::Startup))
.fire(&self.server, &mut ServerLoadEvent::new(LoadType::Startup))
.await;
while !SHOULD_STOP.load(Ordering::Relaxed) {
@@ -645,16 +638,19 @@ fn setup_stdin_console(server: Arc<Server>) {
tokio::spawn(async move {
while !SHOULD_STOP.load(Ordering::Relaxed) {
if let Some(command) = rx.recv().await {
send_cancellable! {{
&server;
ServerCommandEvent::new(command.clone());
'after: {
server.command_dispatcher.read().await
.handle_command(&command::CommandSender::Console.into_source(&server).await, command.as_str())
.await;
};
}}
let mut event = ServerCommandEvent::new(command.clone());
server.plugin_manager.fire(&server, &mut event).await;
if !event.cancelled {
server
.command_dispatcher
.read()
.await
.handle_command(
&command::CommandSender::Console.into_source(&server).await,
command.as_str(),
)
.await;
}
}
}
});
@@ -716,18 +712,20 @@ fn setup_console(mut rl: Editor<PumpkinCommandCompleter, FileHistory>, server: A
};
if let Some(line) = result {
send_cancellable! {{
&server;
ServerCommandEvent::new(line.clone());
'after: {
server.command_dispatcher.read().await
.handle_command(&command::CommandSender::Console.into_source(&server).await, &line)
.await;
let _ = tx_reply.send(1).await;
}
}}
let mut event = ServerCommandEvent::new(line.clone());
server.plugin_manager.fire(&server, &mut event).await;
if !event.cancelled {
server
.command_dispatcher
.read()
.await
.handle_command(
&command::CommandSender::Console.into_source(&server).await,
&line,
)
.await;
let _ = tx_reply.send(1).await;
}
} else {
break;
}

View File

@@ -309,7 +309,7 @@ impl BedrockClient {
pub async fn handle_resource_pack_response(
&self,
packet: SResourcePackResponse,
server: &Server,
server: &Arc<Server>,
) {
// TODO: warn & ignore if the player is already spawned in

View File

@@ -372,8 +372,8 @@ impl BedrockClient {
let mut valid_chunks = Vec::with_capacity(chunks.len());
for chunk in chunks {
let event = ChunkSend::new(player.world(), chunk.clone());
let event = server.plugin_manager.fire(event).await;
let mut event = ChunkSend::new(player.world(), chunk.clone());
server.plugin_manager.fire(&server, &mut event).await;
if !event.cancelled {
valid_chunks.push(chunk.clone());
}
@@ -1106,7 +1106,7 @@ impl BedrockClient {
packet.id,
packet.payload.clone(),
);
event = server.plugin_manager.fire(event).await;
server.plugin_manager.fire(server, &mut event).await;
if event.cancelled {
continue;
}
@@ -1208,7 +1208,7 @@ impl BedrockClient {
.await;
}
SMobEquipment::PACKET_ID => {
self.handle_mob_equipment(player, SMobEquipment::read(reader)?)
self.handle_mob_equipment(server, player, SMobEquipment::read(reader)?)
.await;
}
_ => {

View File

@@ -10,7 +10,6 @@ use pumpkin_data::{
item_stack::ItemStack,
};
use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler};
use pumpkin_macros::send_cancellable;
use pumpkin_protocol::bedrock::{
client::inventory_content::CInventoryContent,
network_item::{
@@ -209,7 +208,7 @@ impl BedrockClient {
&self,
player: &Arc<Player>,
packet: SPlayerAuthInput,
server: &Server,
server: &Arc<Server>,
) {
if !player.has_client_loaded() {
return;
@@ -839,7 +838,7 @@ impl BedrockClient {
}
send_cancellable! {{
server;
&server;
event;
'after: {
server.item_registry.on_use(&stack_for_use, player).await;
@@ -1012,7 +1011,7 @@ impl BedrockClient {
pub async fn handle_chat_message(
&self,
server: &Server,
server: &Arc<Server>,
player: &Arc<Player>,
packet: SText<'_>,
) {
@@ -1236,15 +1235,16 @@ impl BedrockClient {
pub async fn handle_modal_form_response(
&self,
player: &Arc<Player>,
server: &Server,
server: &Arc<Server>,
packet: pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse<'_>,
) {
let event = crate::plugin::api::events::player::bedrock_form_response::BedrockFormResponseEvent::new(
player.clone(),
packet.form_id.0 as u32,
packet.form_data.map(std::borrow::Cow::into_owned),
);
let _ = server.plugin_manager.fire(event).await;
let mut event =
crate::plugin::api::events::player::bedrock_form_response::BedrockFormResponseEvent::new(
player.clone(),
packet.form_id.0 as u32,
packet.form_data.map(std::borrow::Cow::into_owned),
);
server.plugin_manager.fire(server, &mut event).await;
}
#[allow(clippy::too_many_lines)]
@@ -1797,7 +1797,12 @@ impl BedrockClient {
.await;
}
pub async fn handle_mob_equipment(&self, player: &Arc<Player>, packet: SMobEquipment) {
pub async fn handle_mob_equipment(
&self,
_server: &Arc<Server>,
player: &Arc<Player>,
packet: SMobEquipment,
) {
player.update_last_action_time();
let slot = packet.hotbar_slot;
if slot >= 9 {
@@ -1805,9 +1810,13 @@ impl BedrockClient {
}
let previous_slot = player.inventory.get_selected_slot();
if let Some(server) = player.world().server.upgrade() {
let event = PlayerItemHeldEvent::new(player.clone(), previous_slot, slot);
let event = server.plugin_manager.fire(event).await;
if event.cancelled {
let mut event = PlayerItemHeldEvent::new(player.clone(), previous_slot, slot);
server.plugin_manager.fire(&server, &mut event).await;
let is_cancelled = {
use crate::plugin::Cancellable;
event.cancelled()
};
if is_cancelled {
self.enqueue_packet(&CPlayerHotbar {
selected_slot: VarUInt(previous_slot as u32),
container_id: 0,

View File

@@ -354,8 +354,8 @@ impl JavaClient {
self.send_packet_now(&CChunkBatchStart).await;
for chunk in chunks {
let event = ChunkSend::new(player.world(), chunk.clone());
let event = server.plugin_manager.fire(event).await;
let mut event = ChunkSend::new(player.world(), chunk.clone());
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
continue;
}
@@ -393,7 +393,6 @@ impl JavaClient {
} else {
false
};
if !cancelled {
self.enqueue_packet_data(payload).await;
}
@@ -907,7 +906,7 @@ impl JavaClient {
packet.id,
packet.payload.clone(),
);
event = server.plugin_manager.fire(event).await;
server.plugin_manager.fire(server, &mut event).await;
if event.cancelled {
return Ok(());
}
@@ -945,6 +944,7 @@ impl JavaClient {
}
id if id == SClientInformationPlay::to_id(version) => {
self.handle_client_information(
server,
player,
SClientInformationPlay::read(&mut payload, &version)?,
)
@@ -1126,8 +1126,12 @@ impl JavaClient {
.await;
}
id if id == SSetHeldItem::to_id(version) => {
self.handle_set_held_item(player, SSetHeldItem::read(&mut payload, &version)?)
.await;
self.handle_set_held_item(
server,
player,
SSetHeldItem::read(&mut payload, &version)?,
)
.await;
}
id if id == SSetCreativeSlot::to_id(version) => {
self.handle_set_creative_slot(
@@ -1137,7 +1141,7 @@ impl JavaClient {
.await?;
}
id if id == SSwingArm::to_id(version) => {
self.handle_swing_arm(player, SSwingArm::read(&mut payload, &version)?)
self.handle_swing_arm(server, player, SSwingArm::read(&mut payload, &version)?)
.await;
}
id if id == SUpdateSign::to_id(version) => {
@@ -1185,12 +1189,12 @@ impl JavaClient {
}
id if id == SCustomPayload::to_id(version) => {
let payload = SCustomPayload::read(&mut payload, &version)?;
let event = PlayerCustomPayloadEvent::new(
let mut event = PlayerCustomPayloadEvent::new(
player.clone(),
payload.channel.to_string(),
Bytes::copy_from_slice(payload.data),
);
server.plugin_manager.fire(event).await;
server.plugin_manager.fire(server, &mut event).await;
}
id if id == SRecipeBookChangeSettings::to_id(version) => {
self.handle_recipe_book_change_settings(
@@ -1222,12 +1226,12 @@ impl JavaClient {
&mut payload,
&version,
)?;
let event = crate::plugin::api::events::player::custom_click_action::CustomClickActionEvent::new(
let mut event = crate::plugin::api::events::player::custom_click_action::CustomClickActionEvent::new(
player.clone(),
packet.action_id.to_string(),
packet.payload.map(Bytes::copy_from_slice),
);
server.plugin_manager.fire(event).await;
server.plugin_manager.fire(server, &mut event).await;
}
id if id == SSelectTrade::to_id(version) => {
self.handle_select_trade(player, SSelectTrade::read(&mut payload, &version)?)

View File

@@ -51,7 +51,6 @@ use pumpkin_inventory::InventoryError;
use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler};
use pumpkin_macros::send_cancellable;
use pumpkin_protocol::bedrock::client::CMovePlayer;
use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::codec::var_ulong::VarULong;
@@ -986,7 +985,7 @@ impl JavaClient {
&self,
player: &Arc<Player>,
command: SPlayerCommand,
server: &Server,
server: &Arc<Server>,
) {
if command.entity_id != player.entity_id().into() {
return;
@@ -1049,7 +1048,7 @@ impl JavaClient {
&self,
player: &Arc<Player>,
input: SPlayerInput,
server: &Server,
server: &Arc<Server>,
) {
player.last_input.store(input.input, Ordering::Relaxed);
@@ -1345,7 +1344,12 @@ impl JavaClient {
screen_handler_arc.lock().await.send_content_updates().await;
}
pub async fn handle_swing_arm(&self, player: &Arc<Player>, swing_arm: SSwingArm) {
pub async fn handle_swing_arm(
&self,
server: &Arc<Server>,
player: &Arc<Player>,
swing_arm: SSwingArm,
) {
player.update_last_action_time();
let Ok(hand) = Hand::try_from(swing_arm.hand.0) else {
self.kick(TextComponent::text("Invalid hand")).await;
@@ -1378,12 +1382,8 @@ impl JavaClient {
PlayerInteractEvent::new(player, InteractAction::LeftClickAir, &Block::AIR, None)
};
let Some(server) = player.world().server.upgrade() else {
return;
};
send_cancellable! {{
server;
&server;
event;
'after: {
player.swing_hand(hand, false).await;
@@ -1393,7 +1393,7 @@ impl JavaClient {
pub async fn handle_chat_message(
&self,
server: &Server,
server: &Arc<Server>,
player: &Arc<Player>,
chat_message: SChatMessage<'_>,
) {
@@ -1609,6 +1609,7 @@ impl JavaClient {
pub async fn handle_client_information(
&self,
server: &Arc<Server>,
player: &Arc<Player>,
client_information: SClientInformationPlay<'_>,
) {
@@ -1673,9 +1674,9 @@ impl JavaClient {
chunker::update_position(player).await;
}
if main_hand_changed && let Some(server) = player.world().server.upgrade() {
let event = PlayerChangedMainHandEvent::new(player.clone(), main_hand);
let _ = server.plugin_manager.fire(event).await;
if main_hand_changed {
let mut event = PlayerChangedMainHandEvent::new(player.clone(), main_hand);
server.plugin_manager.fire(server, &mut event).await;
}
if update_settings {
@@ -2178,7 +2179,7 @@ impl JavaClient {
&self,
player: &Arc<Player>,
player_abilities: SPlayerAbilities,
server: &Server,
server: &Arc<Server>,
) {
let (flying, allow_flying) = {
let abilities = player.abilities.lock().await;
@@ -2467,7 +2468,7 @@ impl JavaClient {
&self,
player: &Arc<Player>,
use_item: &SUseItem,
server: &Server,
server: &Arc<Server>,
) {
if !player.has_client_loaded() {
return;
@@ -2619,7 +2620,7 @@ impl JavaClient {
async fn should_continue_use_after_fish_event(
&self,
server: &Server,
server: &Arc<Server>,
player: &Arc<Player>,
hand: Hand,
item_for_use: &Item,
@@ -2629,7 +2630,7 @@ impl JavaClient {
}
// TODO: Apply fishing rod durability on retrieval based on catch type.
let fish_event = PlayerFishEvent::new(
let mut fish_event = PlayerFishEvent::new(
player.clone(),
None,
uuid::Uuid::nil(),
@@ -2638,11 +2639,17 @@ impl JavaClient {
hand,
0,
);
let fish_event = server.plugin_manager.fire(fish_event).await;
server.plugin_manager.fire(server, &mut fish_event).await;
!fish_event.cancelled
}
pub async fn handle_set_held_item(&self, player: &Player, held: SSetHeldItem) {
pub async fn handle_set_held_item(
&self,
server: &Arc<Server>,
player: &Player,
held: SSetHeldItem,
) {
player.update_last_action_time();
let slot = held.slot;
if !(0..=8).contains(&slot) {
@@ -2651,19 +2658,17 @@ impl JavaClient {
}
let slot = slot as u8;
let previous_slot = player.inventory.get_selected_slot();
if let Some(server) = player.world().server.upgrade() {
let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id) else {
return;
};
let event = PlayerItemHeldEvent::new(player_arc, previous_slot, slot);
let event = server.plugin_manager.fire(event).await;
if event.cancelled {
player
.client
.enqueue_packet(&CSetSelectedSlot::new(previous_slot as i8))
.await;
return;
}
let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id) else {
return;
};
let mut event = PlayerItemHeldEvent::new(player_arc, previous_slot, slot);
server.plugin_manager.fire(server, &mut event).await;
if event.cancelled {
player
.client
.enqueue_packet(&CSetSelectedSlot::new(previous_slot as i8))
.await;
return;
}
let inv = player.inventory();
@@ -2805,7 +2810,7 @@ impl JavaClient {
&self,
player: &Arc<Player>,
block: &'static Block,
server: &Server,
server: &Arc<Server>,
use_item_on: SUseItemOn,
location: BlockPos,
face: BlockDirection,

View File

@@ -319,7 +319,7 @@ impl Context {
loader: Arc<dyn crate::plugin::loader::PluginLoader>,
) -> bool {
let before_count = self.plugin_manager.loaded_plugins().await.len();
self.plugin_manager.add_loader(loader).await;
self.plugin_manager.add_loader(&self.server, loader).await;
let after_count = self.plugin_manager.loaded_plugins().await.len();
// Return true if any new plugins were loaded

View File

@@ -172,11 +172,8 @@ pub enum PluginState {
pub struct PluginManager {
plugins: RwLock<Vec<LoadedPlugin>>,
loaders: RwLock<Vec<Arc<dyn PluginLoader>>>,
server: RwLock<Option<Arc<Server>>>,
handlers: Arc<RwLock<HandlerMap>>,
unloaded_files: RwLock<HashSet<PathBuf>>,
// Self-reference for sharing with contexts
self_ref: RwLock<Option<Arc<Self>>>,
services: Arc<RwLock<HashMap<String, Arc<dyn Payload>>>>,
// Plugin state tracking
plugin_states: RwLock<HashMap<String, PluginState>>,
@@ -204,43 +201,19 @@ struct LoadedPlugin {
/// Error types for plugin management
#[derive(Error, Debug)]
pub enum ManagerError {
#[error("Server not initialized")]
ServerNotInitialized,
#[error("Plugin not found: {0}")]
PluginNotFound(String),
#[error("Loader error: {0}")]
LoaderError(#[from] LoaderError),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Plugin manager not initialized properly")]
ManagerNotInitialized,
#[error("Dependency error: {0}")]
DependencyError(String),
}
impl Default for PluginManager {
fn default() -> Self {
Self {
plugins: RwLock::new(Vec::new()),
loaders: RwLock::new(vec![
Arc::new(NativePluginLoader),
Arc::new(WasmPluginLoader),
]),
server: RwLock::new(None),
handlers: Arc::new(RwLock::new(HashMap::new())),
unloaded_files: RwLock::new(HashSet::new()),
self_ref: RwLock::new(None),
services: Arc::new(RwLock::new(HashMap::new())),
plugin_states: RwLock::new(HashMap::new()),
state_notify: Arc::new(Notify::new()),
hot_reload_task: RwLock::new(None),
hot_reload_enabled: AtomicBool::new(false),
}
Self::new()
}
}
@@ -248,7 +221,20 @@ impl PluginManager {
/// Create a new plugin manager with default loaders
#[must_use]
pub fn new() -> Self {
Self::default()
Self {
plugins: RwLock::new(Vec::new()),
loaders: RwLock::new(vec![
Arc::new(NativePluginLoader),
Arc::new(WasmPluginLoader),
]),
handlers: Arc::new(RwLock::new(HashMap::new())),
unloaded_files: RwLock::new(HashSet::new()),
services: Arc::new(RwLock::new(HashMap::new())),
plugin_states: RwLock::new(HashMap::new()),
state_notify: Arc::new(Notify::new()),
hot_reload_task: RwLock::new(None),
hot_reload_enabled: AtomicBool::new(false),
}
}
/// Unload all loaded plugins
@@ -272,15 +258,15 @@ impl PluginManager {
}
/// Add a new plugin loader implementation
pub async fn add_loader(&self, loader: Arc<dyn PluginLoader>) {
pub async fn add_loader(self: &Arc<Self>, server: &Arc<Server>, loader: Arc<dyn PluginLoader>) {
self.loaders.write().await.push(loader);
// Try to load previously unloaded files with the new loader
self.retry_unloaded_files().await;
self.retry_unloaded_files(server).await;
}
/// Start watching the plugins directory for changes
pub async fn start_watcher(&self) -> Result<(), ManagerError> {
pub async fn start_watcher(self: &Arc<Self>, server: &Arc<Server>) -> Result<(), ManagerError> {
if self.hot_reload_task.read().await.is_some() {
return Ok(());
}
@@ -302,19 +288,14 @@ impl PluginManager {
.watch(plugin_dir, RecursiveMode::NonRecursive)
.map_err(|e| ManagerError::IoError(std::io::Error::other(e)))?;
let self_ref = self
.self_ref
.read()
.await
.clone()
.ok_or(ManagerError::ManagerNotInitialized)?;
let manager = self.clone();
let server = server.clone();
let task = tokio::spawn(async move {
// Keep watcher alive by moving it into the task
let _watcher = watcher;
while let Some(event) = rx.recv().await {
if !self_ref
if !manager
.hot_reload_enabled
.load(std::sync::atomic::Ordering::Relaxed)
{
@@ -331,7 +312,7 @@ impl PluginManager {
// We need to find if this plugin is already loaded to unload it first
let plugin_name = {
let plugins = self_ref.plugins.read().await;
let plugins = manager.plugins.read().await;
plugins
.iter()
.find(|p| p.path == path)
@@ -340,13 +321,13 @@ impl PluginManager {
if let Some(name) = plugin_name {
info!("Hot-reloading plugin: {}", name);
let _ = self_ref.unload_plugin(&name).await;
let _ = manager.unload_plugin(&name).await;
}
// For now, we just try to load it. If it's already loaded,
// the loader might handle it or we might get a duplicate.
// Most WASM loaders will just create a new instance.
if let Err(e) = self_ref.start_loading_plugin(&path).await {
if let Err(e) = manager.start_loading_plugin(&server, &path).await {
error!("Failed to hot-reload plugin {:?}: {}", path, e);
}
}
@@ -382,13 +363,13 @@ impl PluginManager {
}
/// Retry loading files that couldn't be loaded previously
async fn retry_unloaded_files(&self) {
async fn retry_unloaded_files(self: &Arc<Self>, server: &Arc<Server>) {
let files_to_retry: Vec<PathBuf> =
{ self.unloaded_files.read().await.iter().cloned().collect() };
let mut retry_tasks = Vec::new();
for path in files_to_retry {
if let Ok(task) = self.start_loading_plugin(&path).await {
if let Ok(task) = self.start_loading_plugin(server, &path).await {
retry_tasks.push(task);
}
}
@@ -397,18 +378,6 @@ impl PluginManager {
join_all(retry_tasks).await;
}
/// Set server reference for plugin context
pub async fn set_server(&self, server: Arc<Server>) {
let mut srv = self.server.write().await;
srv.replace(server);
}
/// Set self reference for creating contexts
pub async fn set_self_ref(&self, self_ref: Arc<Self>) {
let mut sref = self.self_ref.write().await;
sref.replace(self_ref);
}
/// Get a clone of the loaders for context use
#[must_use]
pub async fn get_loaders(&self) -> Vec<Arc<dyn PluginLoader>> {
@@ -530,9 +499,10 @@ impl PluginManager {
}
/// Spawn initialization for a single plugin
#[expect(clippy::too_many_lines)]
#[allow(clippy::too_many_lines)]
async fn spawn_plugin_initialization(
&self,
self: &Arc<Self>,
server: Arc<Server>,
instance: Arc<dyn Plugin>,
metadata: PluginMetadata,
loader_data: Box<dyn Any + Send + Sync>,
@@ -545,25 +515,11 @@ impl PluginManager {
.await
.insert(metadata.name.clone(), PluginState::Loading);
let self_ref = self
.self_ref
.read()
.await
.clone()
.ok_or(ManagerError::ServerNotInitialized)?;
let context = Arc::new(Context::new(
metadata.clone(),
Arc::clone(
&self
.server
.read()
.await
.clone()
.ok_or(ManagerError::ServerNotInitialized)?,
),
server,
Arc::clone(&self.handlers),
Arc::clone(&self_ref),
Arc::clone(self),
Arc::clone(&LOGGER_IMPL),
));
@@ -585,7 +541,7 @@ impl PluginManager {
};
// Spawn async task for plugin initialization
let self_ref_clone = Arc::clone(&self_ref);
let self_ref_clone = Arc::clone(self);
let state_notify = Arc::clone(&self.state_notify);
let plugin_name = metadata.name.clone();
let loader_clone = loader.clone();
@@ -662,7 +618,11 @@ impl PluginManager {
}
/// Load all plugins from the plugin directory
pub async fn load_plugins(&self) -> Result<std::time::Duration, ManagerError> {
#[allow(clippy::too_many_lines)]
pub async fn load_plugins(
self: &Arc<Self>,
server: &Arc<Server>,
) -> Result<std::time::Duration, ManagerError> {
let path = Path::new(PLUGIN_DIR);
if !path.exists() {
@@ -748,6 +708,7 @@ impl PluginManager {
if let Some((instance, metadata, loader_data, loader, path)) = plugins_map.remove(&name)
{
let (allowed, wait_time) = self
.clone()
.check_permissions_cached(&path, &metadata, &mut cache, &cache_path)
.await;
@@ -762,7 +723,14 @@ impl PluginManager {
}
match self
.spawn_plugin_initialization(instance, metadata, loader_data, loader, path)
.spawn_plugin_initialization(
server.clone(),
instance,
metadata,
loader_data,
loader,
path,
)
.await
{
Ok(task) => {
@@ -812,7 +780,8 @@ impl PluginManager {
/// Start loading a plugin asynchronously
async fn start_loading_plugin(
&self,
self: &Arc<Self>,
server: &Arc<Server>,
path: &Path,
) -> Result<tokio::task::JoinHandle<()>, ManagerError> {
for loader in self.loaders.read().await.iter() {
@@ -838,6 +807,7 @@ impl PluginManager {
return self
.spawn_plugin_initialization(
server.clone(),
instance,
metadata,
loader_data,
@@ -857,12 +827,19 @@ impl PluginManager {
}
/// Attempt to load a single plugin file
pub async fn try_load_plugin(&self, path: &Path) -> Result<(), ManagerError> {
self.start_loading_plugin(path).await?.await.map_err(|e| {
ManagerError::LoaderError(LoaderError::InitializationFailed(format!(
"Task join error: {e}"
)))
})
pub async fn try_load_plugin(
self: &Arc<Self>,
server: &Arc<Server>,
path: &Path,
) -> Result<(), ManagerError> {
self.start_loading_plugin(server, path)
.await?
.await
.map_err(|e| {
ManagerError::LoaderError(LoaderError::InitializationFailed(format!(
"Task join error: {e}"
)))
})
}
/// Wait for a plugin to finish loading
@@ -1022,28 +999,37 @@ impl PluginManager {
}
/// Fire an event to all registered handlers
pub async fn fire<E: Payload + Send + Sync + 'static>(&self, mut event: E) -> E {
if let Some(server) = self.server.read().await.as_ref() {
let handlers = self.handlers.read().await;
if let Some(handlers) = handlers.get(&E::get_name_static()) {
let (blocking, non_blocking): (Vec<_>, Vec<_>) =
handlers.iter().partition(|h| h.is_blocking());
pub async fn fire<E: Payload + Send + Sync + 'static>(
&self,
server: &Arc<Server>,
event: &mut E,
) {
let handlers_lock = self.handlers.read().await;
if handlers_lock.is_empty() {
return;
}
// Process blocking handlers first
for handler in blocking {
handler.handle_blocking_dyn(server, &mut event).await;
}
let Some(handlers) = handlers_lock.get(&E::get_name_static()) else {
return;
};
// Process non-blocking handlers
join_all(
non_blocking
.into_iter()
.map(|h| h.handle_dyn(server, &event)),
)
.await;
if handlers.is_empty() {
return;
}
// Process blocking handlers first
for handler in handlers {
if handler.is_blocking() {
handler.handle_blocking_dyn(server, event).await;
}
}
// Process non-blocking handlers
for handler in handlers {
if !handler.is_blocking() {
handler.handle_dyn(server, event).await;
}
}
event
}
}

View File

@@ -30,7 +30,6 @@ use pumpkin_world::world::WorldPortalExt;
use tracing::{debug, error, info, warn};
use crate::command::CommandSender;
use pumpkin_macros::send_cancellable;
use pumpkin_protocol::java::client::login::CEncryptionRequest;
use pumpkin_protocol::java::client::play::{CChangeDifficulty, CTabList};
use pumpkin_protocol::{ClientPacket, java::client::config::CPluginMessage};
@@ -481,7 +480,7 @@ impl Server {
///
/// You still have to spawn the `Player` in a `World` to let them join and make them visible.
pub async fn add_player(
&self,
self: &Arc<Self>,
client: Arc<ClientPlatform>,
profile: GameProfile,
config: Option<PlayerConfig>,
@@ -550,7 +549,7 @@ impl Server {
send_cancellable! {{
self;
PlayerLoginEvent::new(player.clone(), TextComponent::text("You have been kicked from the server"));
&mut PlayerLoginEvent::new(player.clone(), TextComponent::text("You have been kicked from the server"));
'after: {
player.screen_handler_sync_handler.store_player(player.clone()).await;
if world
@@ -643,7 +642,7 @@ impl Server {
}
pub async fn broadcast_message(
&self,
self: &Arc<Self>,
message: &TextComponent,
sender_name: &TextComponent,
chat_type: u8,

View File

@@ -25,9 +25,9 @@ impl Ticker {
manager.tick();
let tick_number = server.tick_count.load(Ordering::Relaxed);
let _ = server
server
.plugin_manager
.fire(ServerTickStartEvent::new(tick_number))
.fire(server, &mut ServerTickStartEvent::new(tick_number))
.await;
if manager.is_sprinting() {
@@ -44,9 +44,12 @@ impl Ticker {
let tick_duration_nanos = tick_start_time.elapsed().as_nanos() as i64;
let tick_number = server.tick_count.load(Ordering::Relaxed);
let _ = server
server
.plugin_manager
.fire(ServerTickEndEvent::new(tick_number, tick_duration_nanos))
.fire(
server,
&mut ServerTickEndEvent::new(tick_number, tick_duration_nanos),
)
.await;
server.update_tick_times(tick_duration_nanos).await;

View File

@@ -72,14 +72,11 @@ pub async fn update_position(player: &Arc<Player>) {
.await;
}
}
let mut loading_chunks = Vec::new();
let mut unloading_chunks = Vec::new();
Cylindrical::for_each_changed_chunk(
old_cylindrical,
new_cylindrical,
&mut loading_chunks,
&mut unloading_chunks,
);
let (loading_iter, unloading_iter) =
Cylindrical::changed_chunks(old_cylindrical, new_cylindrical);
let loading_chunks: Vec<_> = loading_iter.collect();
let unloading_chunks: Vec<_> = unloading_iter.collect();
// Use the chunk_manager's world reference, which is updated on dimension change.
// This ensures we load chunks from the correct world after portal teleportation.
@@ -95,13 +92,11 @@ pub async fn update_position(player: &Arc<Player>) {
);
world
};
player.watched_section.store(new_cylindrical);
if let ClientPlatform::Java(_) = player.client.as_ref() {
if let ClientPlatform::Java(client) = player.client.as_ref() {
for chunk in &unloading_chunks {
player
.client
client
.enqueue_packet(&CUnloadChunk::new(chunk.x, chunk.y))
.await;
}

View File

@@ -1818,7 +1818,7 @@ impl World {
&self,
base_config: &BasicConfiguration,
player: Arc<Player>,
server: &Server,
server: &Arc<Server>,
) {
static CREATIVE_CONTENT: std::sync::OnceLock<(Vec<Group>, Vec<Entry>)> =
std::sync::OnceLock::new();
@@ -2557,8 +2557,8 @@ impl World {
)
.color_named(NamedColor::Yellow);
let event = PlayerJoinEvent::new(player.clone(), msg_comp);
let event = server.plugin_manager.fire(event).await;
let mut event = PlayerJoinEvent::new(player.clone(), msg_comp);
server.plugin_manager.fire(server, &mut event).await;
if !event.cancelled {
self.broadcast_system_message(&event.join_message, false)
@@ -3231,9 +3231,9 @@ impl World {
[TextComponent::text(player.gameprofile.name.clone())],
)
.color_named(NamedColor::Yellow);
let event = PlayerJoinEvent::new(player.clone(), msg_comp);
let mut event = PlayerJoinEvent::new(player.clone(), msg_comp);
let event = server.plugin_manager.fire(event).await;
server.plugin_manager.fire(server, &mut event).await;
if !event.cancelled {
self.broadcast_system_message(&event.join_message, false)
@@ -3425,18 +3425,16 @@ impl World {
// the non-cancellable PlayerRespawnEvent, which observes the resolved world.
let (resolved_world, position, yaw, pitch) = if let Some(new_world) = candidate_world {
if let Some(server) = self.server.upgrade() {
let event = server
.plugin_manager
.fire(PlayerChangeWorldEvent {
player: player.clone(),
previous_world: self.clone(),
new_world: new_world.clone(),
position,
yaw,
pitch,
cancelled: false,
})
.await;
let mut event = PlayerChangeWorldEvent {
player: player.clone(),
previous_world: self.clone(),
new_world: new_world.clone(),
position,
yaw,
pitch,
cancelled: false,
};
server.plugin_manager.fire(&server, &mut event).await;
if event.cancelled {
(None, position, yaw, pitch)
@@ -3508,17 +3506,20 @@ impl World {
// Notify plugins that the player has respawned (non-cancellable).
if let Some(server) = self.server.upgrade() {
let _ = server
server
.plugin_manager
.fire(PlayerRespawnEvent::new(
player.clone(),
self.clone(),
target_world.clone(),
position,
yaw,
pitch,
alive,
))
.fire(
&server,
&mut PlayerRespawnEvent::new(
player.clone(),
self.clone(),
target_world.clone(),
position,
yaw,
pitch,
alive,
),
)
.await;
}
@@ -4167,21 +4168,17 @@ impl World {
[TextComponent::text(player.gameprofile.name.clone())],
)
.color_named(NamedColor::Yellow);
let event = PlayerLeaveEvent::new(player.clone(), msg_comp);
let mut event = PlayerLeaveEvent::new(player.clone(), msg_comp);
let event = self
.server
.upgrade()
.unwrap()
.plugin_manager
.fire(event)
.await;
if let Some(server) = self.server.upgrade() {
server.plugin_manager.fire(&server, &mut event).await;
if !event.cancelled {
for player in self.players.load().iter() {
player.send_system_message(&event.leave_message).await;
if !event.cancelled {
for player in self.players.load().iter() {
player.send_system_message(&event.leave_message).await;
}
info!("{}", event.leave_message.to_pretty_console());
}
info!("{}", event.leave_message.to_pretty_console());
}
}
}
@@ -4571,7 +4568,7 @@ impl World {
if is_air(broken_block_state) {
return None;
}
let event = BlockBreakEvent::new(
let mut event = BlockBreakEvent::new(
cause.clone(),
broken_block,
*position,
@@ -4579,13 +4576,12 @@ impl World {
!flags.contains(BlockFlags::SKIP_DROPS),
);
let event = self
.server
.upgrade()
.unwrap()
.plugin_manager
.fire::<BlockBreakEvent>(event)
.await;
if let Some(server) = self.server.upgrade() {
server
.plugin_manager
.fire::<BlockBreakEvent>(&server, &mut event)
.await;
}
if !event.cancelled {
let mut flags = flags;