From 50ea9869616846e5a0448ead8272c397080f36d8 Mon Sep 17 00:00:00 2001 From: "1992414357@qq.com" <1992414357@qq.com> Date: Mon, 21 Apr 2025 06:25:54 +0800 Subject: . --- core/src/client/client.rs | 29 ++++++++++ core/src/client/client_commands.rs | 29 ++++++++++ core/src/client/client_event.rs | 26 +++++++++ core/src/client/mod.rs | 3 + core/src/constants.rs | 9 ++- core/src/lib.rs | 12 ++-- core/src/message_encoder.rs | 71 +++++++++++++++++++++++ core/src/messages.rs | 78 +++++++++++++++++++++++++ core/src/pads_player.rs | 116 ------------------------------------- core/src/pads_player_commands.rs | 28 --------- core/src/pads_server.rs | 99 ------------------------------- core/src/pas_server_commands.rs | 36 ------------ core/src/player/mod.rs | 1 + core/src/player/player_info.rs | 62 ++++++++++++++++++++ core/src/server/mod.rs | 3 + core/src/server/server.rs | 56 ++++++++++++++++++ core/src/server/server_commands.rs | 37 ++++++++++++ core/src/server/server_event.rs | 36 ++++++++++++ 18 files changed, 447 insertions(+), 284 deletions(-) create mode 100644 core/src/client/client.rs create mode 100644 core/src/client/client_commands.rs create mode 100644 core/src/client/client_event.rs create mode 100644 core/src/client/mod.rs create mode 100644 core/src/message_encoder.rs create mode 100644 core/src/messages.rs delete mode 100644 core/src/pads_player.rs delete mode 100644 core/src/pads_player_commands.rs delete mode 100644 core/src/pads_server.rs delete mode 100644 core/src/pas_server_commands.rs create mode 100644 core/src/player/mod.rs create mode 100644 core/src/player/player_info.rs create mode 100644 core/src/server/mod.rs create mode 100644 core/src/server/server.rs create mode 100644 core/src/server/server_commands.rs create mode 100644 core/src/server/server_event.rs (limited to 'core/src') diff --git a/core/src/client/client.rs b/core/src/client/client.rs new file mode 100644 index 0000000..c11bb61 --- /dev/null +++ b/core/src/client/client.rs @@ -0,0 +1,29 @@ +use crate::constants::DEFAULT_PORT; +use crate::player::player_info::PlayerInfo; +use std::net::SocketAddr; + +/// 玩家客户端 +pub struct PadClient { + player_info: PlayerInfo, + remote_addr: SocketAddr, + receive_skin_package : bool +} + +impl Default for PadClient { + + fn default() -> Self { + PadClient { + player_info: PlayerInfo::default(), + remote_addr: SocketAddr::from(([127, 0, 0, 1], DEFAULT_PORT)), + receive_skin_package: true + } + } +} + +impl PadClient { + + /// # 连接到服务器 + pub fn connect_server(&self) { + + } +} \ No newline at end of file diff --git a/core/src/client/client_commands.rs b/core/src/client/client_commands.rs new file mode 100644 index 0000000..f674f6c --- /dev/null +++ b/core/src/client/client_commands.rs @@ -0,0 +1,29 @@ +use crate::client::client::PadClient; +use crate::messages::PlayerMessage; + +impl PadClient { + + // TODO :: 可能需要返回是否成功 + pub fn send_message (&self, msg: PlayerMessage) { + + } + + /// 按下 + pub fn pressed (&self, btn_num: u8) { + self.send_message(PlayerMessage::ButtonPressed(btn_num)) + } + + /// 抬起 + pub fn released (&self, btn_num: u8) { + self.send_message(PlayerMessage::ButtonReleased(btn_num)) + } + + pub fn msg (&self, msg_str: String) { + self.send_message(PlayerMessage::Message(msg_str)) + } + + // TODO :: 可能需要返回信息 + pub fn cmd (&self, cmd_str: String) { + self.send_message(PlayerMessage::Command(cmd_str)) + } +} \ No newline at end of file diff --git a/core/src/client/client_event.rs b/core/src/client/client_event.rs new file mode 100644 index 0000000..ac52e53 --- /dev/null +++ b/core/src/client/client_event.rs @@ -0,0 +1,26 @@ +use crate::messages::ServerMessage; + +/// 玩家客户端事件 +pub trait PadClientEvent { + + /// 加入游戏 + fn joined_game(); + + /// 加入游戏失败 + fn joined_game_failed(); + + /// 退出游戏 + fn left_game(); + + /// 被踢出游戏 + fn kicked(); + + /// 被该游戏封禁(若游戏未保存名单,则游戏重启前无法进入) + fn banned(); + + /// 掉线 + fn disconnected(); + + /// 接收到消息 + fn msg_received(msg: ServerMessage); +} \ No newline at end of file diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs new file mode 100644 index 0000000..0cb7dd2 --- /dev/null +++ b/core/src/client/mod.rs @@ -0,0 +1,3 @@ +pub mod client_commands; +pub mod client; +pub mod client_event; \ No newline at end of file diff --git a/core/src/constants.rs b/core/src/constants.rs index a66f79f..6257c9d 100644 --- a/core/src/constants.rs +++ b/core/src/constants.rs @@ -1 +1,8 @@ -pub const SALT : &str = ":)"; \ No newline at end of file +use bincode::config; +use bincode::config::Configuration; + +pub const SALT : &str = ":)"; + +pub const DEFAULT_PORT : u16 = 8916; + +pub const BINCODE_CONFIG : Configuration = config::standard(); \ No newline at end of file diff --git a/core/src/lib.rs b/core/src/lib.rs index 056b046..d8b1df8 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,5 +1,9 @@ -pub mod pads_server; -pub mod pads_player; +// Public +pub mod client; +pub mod player; +pub mod server; +pub mod messages; + +// Private mod constants; -mod pas_server_commands; -mod pads_player_commands; \ No newline at end of file +mod message_encoder; diff --git a/core/src/message_encoder.rs b/core/src/message_encoder.rs new file mode 100644 index 0000000..a8575d0 --- /dev/null +++ b/core/src/message_encoder.rs @@ -0,0 +1,71 @@ +use std::io::{Read, Write}; +use bincode::error::{DecodeError, EncodeError}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use crate::messages::{ClientMessage, PlayerMessage, ServerCallbackMessage, ServerMessage}; + +pub trait MessageEncoder: Serialize + DeserializeOwned { + fn encode(&self) -> Result; + fn decode(encode: usize) -> Result + where + Self: Sized; +} + +impl MessageEncoder for PlayerMessage { + fn encode(&self) -> Result { + bincode::serde::encode_into_std_write(self, &mut std::io::sink(), bincode::config::standard()) + } + + fn decode(encode: &usize) -> Result { + bincode::serde::decode_from_std_read(&mut &encode[..], bincode::config::standard()) + } +} + +impl MessageEncoder for ServerMessage { + fn encode(&self) -> Result { + bincode::serde::encode_into_std_write(self, &mut std::io::sink(), bincode::config::standard()) + } + + fn decode(encode: usize) -> Result { + bincode::serde::decode_from_std_read(&mut &encode[..], bincode::config::standard()) + } +} + +impl MessageEncoder for ClientMessage { + fn encode(&self) -> Result { + bincode::serde::encode_into_std_write(self, &mut std::io::sink(), bincode::config::standard()) + } + + fn decode(encode: usize) -> Result { + bincode::serde::decode_from_std_read(&mut &encode[..], bincode::config::standard()) + } +} + +impl MessageEncoder for ServerCallbackMessage { + fn encode(&self) -> Result { + bincode::serde::encode_into_std_write(self, &mut std::io::sink(), bincode::config::standard()) + } + + fn decode(encode: usize) -> Result { + bincode::serde::decode_from_std_read(&mut &encode[..], bincode::config::standard()) + } +} + +#[cfg(test)] +mod encoder_tests { + use crate::message_encoder::MessageEncoder; + use crate::messages::PlayerMessage; + + + #[test] + fn test_encode_decode() { + + let msg: String = String::from("Hello, World!"); + + let msg_player = PlayerMessage::Message(msg); + let msg_player_encoded = (&msg_player).encode().expect("Encode failed"); + let msg_player_decoded = PlayerMessage::decode(msg_player_encoded).expect("Decode failed"); + + assert!(msg_player == msg_player_decoded) + } +} \ No newline at end of file diff --git a/core/src/messages.rs b/core/src/messages.rs new file mode 100644 index 0000000..2ae8328 --- /dev/null +++ b/core/src/messages.rs @@ -0,0 +1,78 @@ +use std::time::Duration; +use serde::{Deserialize, Serialize}; +use crate::player::player_info::PlayerInfo; + +/// # 玩家消息 +/// 玩家 --> 服务器 +#[derive(Serialize, Deserialize, PartialEq)] +pub enum PlayerMessage { + /// 消息:发送字符串,服务端无需回复 + Message(String), + + /// 命令:发送字符串,服务器必须回复 + Command(String), + + /// 按钮按下:发送 u8 作为按键代号 + ButtonPressed(u8), + + /// 按钮抬起:发送 u8 作为按键代号 + ButtonReleased(u8), + + /// 轴向:发送 u8 作为轴向代号,两个 f64 分别代表 x 和 y 方向的轴向 + /// u8:轴向代号 + /// f64:X 轴向值 (0 - 1) + /// f64:Y 轴向值 (0 - 1) + Axis(u8, (f64, f64)) +} + +/// # 服务器消息 +/// 服务器 --> 玩家 +#[derive(Serialize, Deserialize, PartialEq)] +pub enum ServerMessage { + + /// 命令:发送字符串,客户端处理 + Command(String), + + /// 动画:发送字符串,客户端触发对应名称动画 + Animation(String), + + /// 震动:发送 Duration 作为时长,f64 作为震动强度 (0 - 1) + Shock(Duration, f64), + + /// 踢出:发送踢出指令,以便让客户端明白自己的掉线是因为被踢出 + /// “杀人还要诛心!!!好可怕啊!” + Kick +} + +/// # 客户端消息 +/// 客户端 --> 服务器 +#[derive(Serialize, Deserialize, PartialEq)] +pub enum ClientMessage { + + /// 连接请求,将和服务端建立连接,并通知服务器将要建立连接的玩家 + ConnectRequest(PlayerInfo), + + /// 下载布局请求 + RequestLayout, + + /// 下载皮肤请求 + RequestSkin, + + /// 客户端准备就绪 + Ready +} + +/// # 服务器回调消息 +/// 服务器 --> 客户端 +#[derive(Serialize, Deserialize, PartialEq)] +pub enum ServerCallbackMessage { + + /// 拒绝:发送消息已通知客户端请求被拒绝 + Refuse, + + /// 接收:发送消息已通知客户端请求被接受,以继续操作 + Accept, + + /// 欢迎:客户端准备完成后,服务端发送 Welcome 以允许客户端以玩家加入游戏 + Welcome +} \ No newline at end of file diff --git a/core/src/pads_player.rs b/core/src/pads_player.rs deleted file mode 100644 index eb9c661..0000000 --- a/core/src/pads_player.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::net::SocketAddr; -use hex::encode; -use serde::{Deserialize, Serialize}; -use sha1::{Digest, Sha1}; -use crate::constants::SALT; -use crate::pads_server::ServerMessage; - -/// 玩家客户端 -pub struct PadClient { - player_info: PlayerInfo, - remote_addr: SocketAddr, - receive_skin_package : bool -} - -impl Default for PadClient { - - fn default() -> Self { - PadClient { - player_info: PlayerInfo::default(), - remote_addr: SocketAddr::from(([127, 0, 0, 1], 8916)), - receive_skin_package: true - } - } -} - -/// 玩家客户端事件 -pub trait PadClientEvent { - - /// 加入游戏 - fn joined_game(); - - /// 加入游戏失败 - fn joined_game_failed(); - - /// 退出游戏 - fn left_game(); - - /// 被踢出游戏 - fn kicked(); - - /// 被该游戏封禁(若游戏未保存名单,则游戏重启前无法进入) - fn banned(); - - /// 掉线 - fn disconnected(); - - /// 接收到消息 - fn msg_received(msg: ServerMessage); -} - -/// 玩家信息 -pub struct PlayerInfo { - pub display_name: String, - pub name_hash: String, - - // 颜色 HSV - pub color_hue: i32, // 0 - 360 - pub color_saturation: f64, // 0 - 1 - pub color_value: f64 // 0 - 1 -} - -impl Default for PlayerInfo { - - fn default() -> Self { - PlayerInfo { - display_name: String::from("Player"), - name_hash: String::new(), - color_hue: 86, - color_saturation: 1.0, - color_value: 1.0, - } - } -} - -/// 玩家消息 -#[derive(Serialize, Deserialize, PartialEq)] -pub enum PlayerMessage { - Message(String), - Command(String), - ButtonPressed(u8), - ButtonReleased(u8), - Axis(u8, (f64, f64)) -} - -impl PlayerInfo { - - /// # 配置玩家 ID 和 名称 - /// self : 自身 - /// display_name : 玩家显示名称 - /// id : 玩家 ID - /// return -> 自身 - pub fn name(&mut self, display_name: String, id: String) -> &mut Self { - self.display_name = display_name; - - let combined = format!("{}{}{}", &self.display_name, id, SALT); - let mut hasher = Sha1::new(); - hasher.update(combined); - let result = hasher.finalize(); - - self.name_hash = encode(&result[..]); - self - } - - /// # 配置玩家颜色 - /// self : 自身 - /// hue : 色调 0 -> RED; 120 -> GREEN; 240 -> BLUE - /// saturation : 饱和度 - /// value : 明度 - /// return -> 自身 - pub fn color_hsv(&mut self, hue: i32, saturation: f64, value: f64) -> &mut Self { - self.color_hue = hue.clamp(0, 360); - self.color_saturation = saturation.clamp(0.0, 1.0); - self.color_value = value.clamp(0.0, 1.0); - self - } -} \ No newline at end of file diff --git a/core/src/pads_player_commands.rs b/core/src/pads_player_commands.rs deleted file mode 100644 index 276805f..0000000 --- a/core/src/pads_player_commands.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::pads_player::{PadClient, PlayerMessage}; - -impl PadClient { - - // TODO :: 可能需要返回是否成功 - pub fn send_message (&self, msg: PlayerMessage) { - - } - - /// 按下 - pub fn pressed (&self, btn_num: u8) { - self.send_message(PlayerMessage::ButtonPressed(btn_num)) - } - - /// 抬起 - pub fn released (&self, btn_num: u8) { - self.send_message(PlayerMessage::ButtonReleased(btn_num)) - } - - pub fn msg (&self, msg_str: String) { - self.send_message(PlayerMessage::Message(msg_str)) - } - - // TODO :: 可能需要返回信息 - pub fn cmd (&self, cmd_str: String) { - self.send_message(PlayerMessage::Command(cmd_str)) - } -} \ No newline at end of file diff --git a/core/src/pads_server.rs b/core/src/pads_server.rs deleted file mode 100644 index d10a88b..0000000 --- a/core/src/pads_server.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::net::{SocketAddr, UdpSocket}; -use std::time::Duration; -use bincode::Encode; -use crate::pads_player::{PlayerInfo, PlayerMessage}; - -/// # 监听服务器 -/// 监听服务器用于连接玩家设备,并接收玩家的按键输入 -pub struct PadServer { - server_name: String, - port: u16 -} - -impl Default for PadServer { - - /// # 使用默认布局部署服务器 - /// 创建一个使用默认布局的服务器 - fn default() -> Self { - PadServer { - server_name: String::from("This Is No Pads"), - port: 8916 - // TODO :: 补充布局文件 - } - } -} - -impl PadServer { - - /// # 开始监听服务器 - /// self : 服务器 - /// event : 事件 - pub fn start_listening(&self, event : impl PadServerEvent) { - let socket_addr = SocketAddr::from(([127, 0, 0, 1], self.port)); - match UdpSocket::bind(socket_addr) { - Ok(socket) => { - let mut buf = vec![0u8; 1024]; - - loop { - // 接收连接请求 - match socket.recv_from(&mut buf) { - Ok((size, src)) => { - - } - Err(_) => {} - } - } - } - - Err(_) => {} - } - } - - /// # 停止监听服务器 - /// self : 服务器 - pub fn stop_listening(&self) { - - } -} - -/// # 监听服务器事件 -/// 服务器运行过程中的信息将会发送到该 Event Trait -pub trait PadServerEvent { - - /// 服务器启动时 - fn server_start(); - - /// 服务器关闭时 - fn server_shutdown(); - - /// 玩家加入请求 - /// info : 玩家信息 - /// return -> 是否允许加入服务器 - fn join_request_received(info: PlayerInfo) -> bool; - - /// 玩家加入游戏 - /// info : 玩家信息 - fn player_joined(info: PlayerInfo); - - /// 玩家离开游戏 - /// info : 玩家信息 - fn player_left(info: PlayerInfo); - - /// 玩家断开连接 - /// info : 玩家信息 - fn player_disconnected(info: PlayerInfo); - - /// 接收到消息 - /// info : 玩家信息 - /// msg : 接受的消息 - fn msg_received(info: PlayerInfo, msg: PlayerMessage); -} - -/// # 监听服务器消息 -/// 监听服务器用于向玩家发送指令 -pub enum ServerMessage { - Command(String), - Animation(String), - Shock(Duration, f64), - Kick -} \ No newline at end of file diff --git a/core/src/pas_server_commands.rs b/core/src/pas_server_commands.rs deleted file mode 100644 index 8d15261..0000000 --- a/core/src/pas_server_commands.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::time::Duration; -use crate::pads_server::{PadServer, ServerMessage}; - -impl PadServer { - - // TODO :: 可能需要返回是否成功 - pub fn send_message (&self, player_index: i32, msg: ServerMessage) { - - } - - // TODO :: 可能需要返回是否成功 - pub fn kick (&self, player_index: i32, ban: bool) { - self.send_message(player_index, ServerMessage::Kick); - - // TODO :: 封禁模式启动 - if ban { - - } - } - - pub fn shock (&self, player_index: i32, duration: Duration, value: f64) { - self.send_message(player_index, ServerMessage::Shock(duration, value)); - } - - pub fn set_ignore_control (&self, player_index: i32, ignore: bool) { - - } - - pub fn set_ignore_message (&self, player_index: i32, ignore: bool) { - - } - - pub fn set_ignore_command (&self, player_index: i32, ignore: bool) { - - } -} \ No newline at end of file diff --git a/core/src/player/mod.rs b/core/src/player/mod.rs new file mode 100644 index 0000000..fcb7558 --- /dev/null +++ b/core/src/player/mod.rs @@ -0,0 +1 @@ +pub mod player_info; \ No newline at end of file diff --git a/core/src/player/player_info.rs b/core/src/player/player_info.rs new file mode 100644 index 0000000..6412b66 --- /dev/null +++ b/core/src/player/player_info.rs @@ -0,0 +1,62 @@ +use hex::encode; +use serde::{Deserialize, Serialize}; +use sha1::{Digest, Sha1}; +use crate::constants::SALT; + +/// 玩家信息 +#[derive(Serialize, Deserialize, PartialEq)] +pub struct PlayerInfo { + pub display_name: String, + pub name_hash: String, + + // 颜色 HSV + pub color_hue: i32, // 0 - 360 + pub color_saturation: f64, // 0 - 1 + pub color_value: f64, // 0 - 1 +} + +impl Default for PlayerInfo { + + fn default() -> Self { + PlayerInfo { + display_name: String::from("Player"), + name_hash: String::new(), + color_hue: 86, + color_saturation: 1.0, + color_value: 1.0, + } + } +} + +impl PlayerInfo { + + /// # 配置玩家 ID 和 名称 + /// self : 自身 + /// display_name : 玩家显示名称 + /// id : 玩家 ID + /// return -> 自身 + pub fn name(&mut self, display_name: String, id: String) -> &mut Self { + self.display_name = display_name; + + let combined = format!("{}{}{}", &self.display_name, id, SALT); + let mut hasher = Sha1::new(); + hasher.update(combined); + let result = hasher.finalize(); + + self.name_hash = encode(&result[..]); + self + } + + /// # 配置玩家颜色 + /// self : 自身 + /// hue : 色调 0 -> RED; 120 -> GREEN; 240 -> BLUE + /// saturation : 饱和度 + /// value : 明度 + /// return -> 自身 + pub fn color_hsv(&mut self, hue: i32, saturation: f64, value: f64) -> &mut Self { + self.color_hue = hue.clamp(0, 360); + self.color_saturation = saturation.clamp(0.0, 1.0); + self.color_value = value.clamp(0.0, 1.0); + self + } +} \ No newline at end of file diff --git a/core/src/server/mod.rs b/core/src/server/mod.rs new file mode 100644 index 0000000..d7553a8 --- /dev/null +++ b/core/src/server/mod.rs @@ -0,0 +1,3 @@ +pub mod server; +pub mod server_commands; +pub mod server_event; \ No newline at end of file diff --git a/core/src/server/server.rs b/core/src/server/server.rs new file mode 100644 index 0000000..c71892c --- /dev/null +++ b/core/src/server/server.rs @@ -0,0 +1,56 @@ +use crate::constants::DEFAULT_PORT; +use crate::server::server_event::PadServerEvent; +use std::net::{SocketAddr, UdpSocket}; + +/// # 监听服务器 +/// 监听服务器用于连接玩家设备,并接收玩家的按键输入 +pub struct PadServer { + server_name: String, + port: u16 +} + +impl Default for PadServer { + + /// # 使用默认布局部署服务器 + /// 创建一个使用默认布局的服务器 + fn default() -> Self { + PadServer { + server_name: String::from("This Is No Pads"), + port: DEFAULT_PORT + // TODO :: 补充布局文件 + } + } +} + +impl PadServer { + + /// # 开始监听服务器 + /// self : 服务器 + /// event : 事件 + pub fn start_listening(&self, event : impl PadServerEvent) { + let socket_addr = SocketAddr::from(([127, 0, 0, 1], self.port)); + match UdpSocket::bind(socket_addr) { + Ok(socket) => { + let mut buf = vec![0u8; 1024]; + + loop { + // 接收连接请求 + match socket.recv_from(&mut buf) { + Ok((size, src)) => { + + } + Err(_) => {} + } + } + } + + Err(_) => {} + } + } + + /// # 停止监听服务器 + /// self : 服务器 + pub fn stop_listening(&self) { + + } +} \ No newline at end of file diff --git a/core/src/server/server_commands.rs b/core/src/server/server_commands.rs new file mode 100644 index 0000000..dcf40a3 --- /dev/null +++ b/core/src/server/server_commands.rs @@ -0,0 +1,37 @@ +use std::time::Duration; +use crate::messages::ServerMessage; +use crate::server::server::PadServer; + +impl PadServer { + + // TODO :: 可能需要返回是否成功 + pub fn send_message (&self, player_index: i32, msg: ServerMessage) { + + } + + // TODO :: 可能需要返回是否成功 + pub fn kick (&self, player_index: i32, ban: bool) { + self.send_message(player_index, ServerMessage::Kick); + + // TODO :: 封禁模式启动 + if ban { + + } + } + + pub fn shock (&self, player_index: i32, duration: Duration, value: f64) { + self.send_message(player_index, ServerMessage::Shock(duration, value)); + } + + pub fn set_ignore_control (&self, player_index: i32, ignore: bool) { + + } + + pub fn set_ignore_message (&self, player_index: i32, ignore: bool) { + + } + + pub fn set_ignore_command (&self, player_index: i32, ignore: bool) { + + } +} \ No newline at end of file diff --git a/core/src/server/server_event.rs b/core/src/server/server_event.rs new file mode 100644 index 0000000..f63df5f --- /dev/null +++ b/core/src/server/server_event.rs @@ -0,0 +1,36 @@ +use crate::messages::PlayerMessage; +use crate::player::player_info::PlayerInfo; + +/// # 监听服务器事件 +/// 服务器运行过程中的信息将会发送到该 Event Trait +pub trait PadServerEvent { + + /// 服务器启动时 + fn server_start(); + + /// 服务器关闭时 + fn server_shutdown(); + + /// 玩家加入请求 + /// info : 玩家信息 + /// return -> 是否允许加入服务器 + fn join_request_received(info: PlayerInfo) -> bool; + + /// 玩家加入游戏 + /// info : 玩家信息 + fn player_joined(info: PlayerInfo); + + /// 玩家离开游戏 + /// info : 玩家信息 + fn player_left(info: PlayerInfo); + + /// 玩家断开连接 + /// info : 玩家信息 + fn player_disconnected(info: PlayerInfo); + + /// 接收到消息 + /// info : 玩家信息 + /// msg : 接受的消息 + fn msg_received(info: PlayerInfo, msg: PlayerMessage); +} + -- cgit