mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Add ServerListPingEvent (#2736)
* feat(events): add ServerListPingEvent * Update pumpkin-plugin-wit to 50962bf
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
pub mod server_broadcast;
|
||||
/// Server command execution event.
|
||||
pub mod server_command;
|
||||
/// Server list ping response event.
|
||||
pub mod server_list_ping;
|
||||
/// Server initialization load event.
|
||||
pub mod server_load;
|
||||
/// Server tick completion event.
|
||||
@@ -13,6 +15,7 @@ pub mod spawn_change;
|
||||
|
||||
pub use server_broadcast::*;
|
||||
pub use server_command::*;
|
||||
pub use server_list_ping::*;
|
||||
pub use server_load::*;
|
||||
pub use server_tick_end::*;
|
||||
pub use server_tick_start::*;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
use crate::wit::pumpkin::plugin::event::{Event, EventType, ServerListPingEventData};
|
||||
|
||||
use super::super::FromIntoEvent;
|
||||
|
||||
/// Fires when the server prepares a Java status/list ping response.
|
||||
///
|
||||
/// Register this as a blocking event handler to customize the MOTD, favicon,
|
||||
/// and reported player counts for WASM plugins.
|
||||
pub struct ServerListPingEvent;
|
||||
|
||||
impl FromIntoEvent for ServerListPingEvent {
|
||||
const EVENT_TYPE: EventType = EventType::ServerListPingEvent;
|
||||
type Data = ServerListPingEventData;
|
||||
|
||||
fn data_from_event(event: Event) -> Self::Data {
|
||||
match event {
|
||||
Event::ServerListPingEvent(data) => data,
|
||||
_ => panic!("expected ServerListPingEvent"),
|
||||
}
|
||||
}
|
||||
|
||||
fn data_into_event(data: Self::Data) -> Event {
|
||||
Event::ServerListPingEvent(data)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
//! WIT-generated plugin type extensions belong in here. For example [Display](std::fmt::Display) implementations.
|
||||
|
||||
mod server_list_ping;
|
||||
mod uuid;
|
||||
|
||||
38
crates/pumpkin-plugin-api/src/ext/server_list_ping.rs
Normal file
38
crates/pumpkin-plugin-api/src/ext/server_list_ping.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::wit::pumpkin::plugin::event::ServerListPingAddress;
|
||||
|
||||
impl fmt::Display for ServerListPingAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.host.contains(':') {
|
||||
write!(f, "[{}]:{}", self.host, self.port)
|
||||
} else {
|
||||
write!(f, "{}:{}", self.host, self.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn formats_ipv4_address() {
|
||||
let address = ServerListPingAddress {
|
||||
host: "127.0.0.1".into(),
|
||||
port: 25565,
|
||||
};
|
||||
|
||||
assert_eq!(address.to_string(), "127.0.0.1:25565");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formats_ipv6_address() {
|
||||
let address = ServerListPingAddress {
|
||||
host: "::1".into(),
|
||||
port: 25565,
|
||||
};
|
||||
|
||||
assert_eq!(address.to_string(), "[::1]:25565");
|
||||
}
|
||||
}
|
||||
Submodule crates/pumpkin-plugin-wit updated: ab193d1160...50962bf639
@@ -227,7 +227,7 @@ impl PendingConnection {
|
||||
|
||||
async fn handle_status_packet(
|
||||
&mut self,
|
||||
server: &Server,
|
||||
server: &Arc<Server>,
|
||||
packet: &RawPacket,
|
||||
) -> Result<Option<PacketHandlerResult>, ReadingError> {
|
||||
debug!("Handling status group");
|
||||
|
||||
@@ -1,21 +1,59 @@
|
||||
use pumpkin_protocol::{
|
||||
java::client::status::CPingResponse, java::server::status::SStatusPingRequest,
|
||||
Players,
|
||||
java::client::status::{CPingResponse, CStatusResponse},
|
||||
java::server::status::SStatusPingRequest,
|
||||
};
|
||||
|
||||
use crate::{net::java::pending::PendingConnection, server::Server};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
net::java::pending::PendingConnection, plugin::server::list_ping::ServerListPingEvent,
|
||||
server::Server,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
impl PendingConnection {
|
||||
pub async fn handle_status_request(&mut self, server: &Server) {
|
||||
pub async fn handle_status_request(&mut self, server: &Arc<Server>) {
|
||||
debug!("Handling status request");
|
||||
let status = server.get_status();
|
||||
self.send_packet_now(
|
||||
&status
|
||||
let mut status_response = {
|
||||
let status = server.get_status();
|
||||
status
|
||||
.lock()
|
||||
.await
|
||||
.get_status_packet(self.version.load().protocol_version()),
|
||||
)
|
||||
.await;
|
||||
.get_status_response(self.version.load().protocol_version())
|
||||
};
|
||||
|
||||
let (max_players, num_players) = status_response
|
||||
.players
|
||||
.as_ref()
|
||||
.map_or((0, 0), |players| (players.max, players.online));
|
||||
|
||||
let mut event = ServerListPingEvent::new(
|
||||
self.server_address.clone(),
|
||||
self.address,
|
||||
status_response.description.clone(),
|
||||
max_players,
|
||||
num_players,
|
||||
status_response.favicon.clone(),
|
||||
);
|
||||
server.plugin_manager.fire(server, &mut event).await;
|
||||
|
||||
status_response.description = event.motd;
|
||||
status_response.favicon = event.favicon;
|
||||
if let Some(players) = &mut status_response.players {
|
||||
players.max = event.max_players;
|
||||
players.online = event.num_players;
|
||||
} else {
|
||||
status_response.players = Some(Players {
|
||||
max: event.max_players,
|
||||
online: event.num_players,
|
||||
sample: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let status_json = serde_json::to_string(&status_response).unwrap_or_default();
|
||||
self.send_packet_now(&CStatusResponse::new(status_json))
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_ping_request(&mut self, ping_request: SStatusPingRequest) {
|
||||
|
||||
111
crates/pumpkin/src/plugin/api/events/server/list_ping.rs
Normal file
111
crates/pumpkin/src/plugin/api/events/server/list_ping.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::{
|
||||
fmt,
|
||||
net::{SocketAddr, ToSocketAddrs},
|
||||
};
|
||||
|
||||
use pumpkin_macros::Event;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ServerListPingAddress {
|
||||
host: String,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
impl ServerListPingAddress {
|
||||
#[must_use]
|
||||
pub const fn new(host: String, port: u16) -> Self {
|
||||
Self { host, port }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_socket_addr(address: SocketAddr) -> Self {
|
||||
Self {
|
||||
host: address.ip().to_string(),
|
||||
port: address.port(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn host(&self) -> &str {
|
||||
&self.host
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn port(&self) -> u16 {
|
||||
self.port
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn as_socket_addr(&self) -> Option<SocketAddr> {
|
||||
(self.host.as_str(), self.port)
|
||||
.to_socket_addrs()
|
||||
.ok()
|
||||
.and_then(|mut addrs| addrs.next())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ServerListPingAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.host.contains(':') {
|
||||
write!(f, "[{}]:{}", self.host, self.port)
|
||||
} else {
|
||||
write!(f, "{}:{}", self.host, self.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that occurs when the server responds to a status ping.
|
||||
#[derive(Event, Clone)]
|
||||
pub struct ServerListPingEvent {
|
||||
/// The hostname the client used to ping the server.
|
||||
pub(crate) hostname: String,
|
||||
|
||||
/// The address the ping came from.
|
||||
pub(crate) address: ServerListPingAddress,
|
||||
|
||||
/// The MOTD shown in the server list.
|
||||
pub motd: String,
|
||||
|
||||
/// The maximum player count.
|
||||
pub max_players: u32,
|
||||
|
||||
/// The current online player count.
|
||||
pub num_players: u32,
|
||||
|
||||
/// The favicon as a data URI (if any).
|
||||
pub favicon: Option<String>,
|
||||
}
|
||||
|
||||
impl ServerListPingEvent {
|
||||
/// Creates a new `ServerListPingEvent`.
|
||||
#[must_use]
|
||||
pub fn new(
|
||||
hostname: String,
|
||||
address: SocketAddr,
|
||||
motd: String,
|
||||
max_players: u32,
|
||||
num_players: u32,
|
||||
favicon: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
hostname,
|
||||
address: ServerListPingAddress::from_socket_addr(address),
|
||||
motd,
|
||||
max_players,
|
||||
num_players,
|
||||
favicon,
|
||||
}
|
||||
}
|
||||
|
||||
/// The hostname provided by the client during the status handshake.
|
||||
#[must_use]
|
||||
pub fn hostname(&self) -> &str {
|
||||
&self.hostname
|
||||
}
|
||||
|
||||
/// The remote socket address of the client requesting the status ping.
|
||||
#[must_use]
|
||||
pub const fn address(&self) -> &ServerListPingAddress {
|
||||
&self.address
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod list_ping;
|
||||
pub mod packet;
|
||||
pub mod server_broadcast;
|
||||
pub mod server_command;
|
||||
|
||||
@@ -443,6 +443,7 @@ async fn register_server_event(
|
||||
event_type: EventType,
|
||||
) {
|
||||
use crate::plugin::server::{
|
||||
list_ping::ServerListPingEvent,
|
||||
packet::{PacketReceivedEvent, PacketSentEvent},
|
||||
server_broadcast::ServerBroadcastEvent,
|
||||
server_command::ServerCommandEvent,
|
||||
@@ -462,6 +463,10 @@ async fn register_server_event(
|
||||
EventType::ServerCommandEvent => {
|
||||
register_typed_event::<ServerCommandEvent>(resource, handler, priority, blocking).await;
|
||||
}
|
||||
EventType::ServerListPingEvent => {
|
||||
register_typed_event::<ServerListPingEvent>(resource, handler, priority, blocking)
|
||||
.await;
|
||||
}
|
||||
EventType::ServerBroadcastEvent => {
|
||||
register_typed_event::<ServerBroadcastEvent>(resource, handler, priority, blocking)
|
||||
.await;
|
||||
@@ -544,6 +549,7 @@ impl pumpkin::plugin::context::HostContext for PluginHostState {
|
||||
event_type @ (EventType::PacketReceivedEvent
|
||||
| EventType::PacketSentEvent
|
||||
| EventType::ServerCommandEvent
|
||||
| EventType::ServerListPingEvent
|
||||
| EventType::ServerBroadcastEvent
|
||||
| EventType::ServerLoadEvent
|
||||
| EventType::ServerTickEndEvent
|
||||
|
||||
@@ -47,6 +47,16 @@ pub trait ToFromWasmEvent {
|
||||
event: wit::v0_1::pumpkin::plugin::event::Event,
|
||||
state: &mut PluginHostState,
|
||||
) -> Self;
|
||||
|
||||
fn apply_wasm_event(
|
||||
&mut self,
|
||||
event: wit::v0_1::pumpkin::plugin::event::Event,
|
||||
state: &mut PluginHostState,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
*self = Self::from_wasm_event(event, state);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const fn to_wasm_position(position: Vector3<f64>) -> pumpkin::plugin::common::Position {
|
||||
@@ -244,7 +254,7 @@ impl<E: Payload + ToFromWasmEvent> EventHandler<E> for WasmPluginEventHandler {
|
||||
.call_handle_event(&mut *store, self.handler_id, server, &wasm_event)
|
||||
.await
|
||||
{
|
||||
*event = E::from_wasm_event(returned_event, store.data_mut());
|
||||
event.apply_wasm_event(returned_event, store.data_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ use crate::plugin::{
|
||||
generated_packets,
|
||||
pumpkin::plugin::event::{
|
||||
ClientboundPacket, Event, PacketReceivedEventData, PacketSentEventData,
|
||||
ServerBroadcastEventData, ServerCommandEventData, ServerLoadEventData,
|
||||
ServerLoadType, ServerTickEndEventData, ServerTickStartEventData,
|
||||
ServerboundPacket,
|
||||
ServerBroadcastEventData, ServerCommandEventData, ServerListPingAddress,
|
||||
ServerListPingEventData, ServerLoadEventData, ServerLoadType,
|
||||
ServerTickEndEventData, ServerTickStartEventData, ServerboundPacket,
|
||||
},
|
||||
},
|
||||
},
|
||||
server::{
|
||||
list_ping::ServerListPingEvent,
|
||||
packet::{PacketReceivedEvent, PacketSentEvent},
|
||||
server_broadcast::ServerBroadcastEvent,
|
||||
server_command::ServerCommandEvent,
|
||||
@@ -154,6 +155,51 @@ impl ToFromWasmEvent for ServerBroadcastEvent {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFromWasmEvent for ServerListPingEvent {
|
||||
fn to_wasm_event(&self, _state: &mut PluginHostState) -> Event {
|
||||
Event::ServerListPingEvent(ServerListPingEventData {
|
||||
hostname: self.hostname().to_string(),
|
||||
address: ServerListPingAddress {
|
||||
host: self.address().host().to_string(),
|
||||
port: self.address().port(),
|
||||
},
|
||||
motd: self.motd.clone(),
|
||||
max_players: self.max_players,
|
||||
num_players: self.num_players,
|
||||
favicon: self.favicon.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn from_wasm_event(event: Event, _state: &mut PluginHostState) -> Self {
|
||||
match event {
|
||||
Event::ServerListPingEvent(data) => Self {
|
||||
hostname: data.hostname,
|
||||
address: crate::plugin::api::events::server::list_ping::ServerListPingAddress::new(
|
||||
data.address.host,
|
||||
data.address.port,
|
||||
),
|
||||
motd: data.motd,
|
||||
max_players: data.max_players,
|
||||
num_players: data.num_players,
|
||||
favicon: data.favicon,
|
||||
},
|
||||
_ => panic!("unexpected event type"),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_wasm_event(&mut self, event: Event, _state: &mut PluginHostState) {
|
||||
match event {
|
||||
Event::ServerListPingEvent(data) => {
|
||||
self.motd = data.motd;
|
||||
self.max_players = data.max_players;
|
||||
self.num_players = data.num_players;
|
||||
self.favicon = data.favicon;
|
||||
}
|
||||
_ => panic!("unexpected event type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToFromWasmEvent for ServerLoadEvent {
|
||||
fn to_wasm_event(&self, _state: &mut PluginHostState) -> Event {
|
||||
Event::ServerLoadEvent(ServerLoadEventData {
|
||||
|
||||
@@ -90,7 +90,7 @@ impl CachedStatus {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_status_packet(&self, client_protocol: i32) -> CStatusResponse {
|
||||
pub fn get_status_response(&self, client_protocol: i32) -> StatusResponse {
|
||||
let mut response = self.status_response.clone();
|
||||
|
||||
let supported_min = LOWEST_SUPPORTED_MC_VERSION.protocol_version();
|
||||
@@ -103,8 +103,12 @@ impl CachedStatus {
|
||||
version.protocol = client_protocol as u32;
|
||||
}
|
||||
|
||||
let json = serde_json::to_string(&response).unwrap_or_default();
|
||||
response
|
||||
}
|
||||
|
||||
pub fn get_status_packet(&self, client_protocol: i32) -> CStatusResponse {
|
||||
let response = self.get_status_response(client_protocol);
|
||||
let json = serde_json::to_string(&response).unwrap_or_default();
|
||||
CStatusResponse::new(json)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user