网络
Pumpkin 中的大部分网络代码可以在 pumpkin-protocol 包中找到。
服务器绑定(Serverbound):客户端 → 服务器
客户端绑定(Clientbound):服务器 → 客户端
结构
Pumpkin 协议中的数据包按功能和状态组织。
server
:包含服务器绑定数据包的定义。
client
:包含客户端绑定数据包的定义。
状态
握手(Handshake):始终是由客户端发送的第一个数据包。这也决定了下一个状态,通常用于指示玩家是想执行状态请求、加入服务器,还是想被转移。
状态(Status):表示客户端想要查看状态响应(MOTD)。
登录(Login):登录序列。表示客户端想要加入服务器。
配置(Config):配置数据包序列主要从服务器发送到客户端(功能、资源包、服务器链接等)。
游戏(Play):最终状态,表示玩家现在准备好加入,也用于处理所有其他游戏玩法数据包。
Minecraft 协议
您可以在 https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol 找到所有 Minecraft Java 数据包。在那里,您还可以看到它们处于哪个状态。 您还可以看到数据包的所有信息,根据它们是服务器绑定还是客户端绑定,我们可以读取或写入这些信息。
添加数据包
- 添加数据包很简单。首先,派生:
// 对于客户端绑定数据包:
#[derive(Serialize)]
// 对于服务器绑定数据包:
#[derive(Deserialize)]
- 接下来,您必须让系统知道您的结构表示一个数据包。这会自动从 JSON 数据包文件中获取数据包 ID。
// 对于客户端绑定数据包:
#[client_packet("play:disconnect")]
// 对于服务器绑定数据包:
#[server_packet("login:move_player_pos")]
- 现在您可以创建
struct
。
IMPORTANT
请以 "C" 或 "S" 开始数据包名称,分别代表"客户端绑定"或"服务器绑定"。 此外,如果这是一个可以在多个状态下发送的数据包,请将状态添加到名称中。例如,有 3 个不同的断开连接数据包。
CLoginDisconnect
CConfigDisconnect
CPlayDisconnect
在您的数据包结构中创建字段,以表示将要发送的数据。
IMPORTANT
使用描述性字段名称和适当的数据类型。
例子:
pub struct CPlayDisconnect {
reason: TextComponent,
// 更多字段...
}
pub struct SPlayerPosition {
pub x: f64,
pub feet_y: f64,
pub z: f64,
pub ground: bool,
}
- (仅限客户端绑定数据包)
impl
一个new
函数,这样我们就可以通过输入值来实际创建它们。
impl CPlayDisconnect {
pub fn new(reason: TextComponent) -> Self {
Self { reason }
}
}
- 最后,所有内容应该组合在一起。
#[derive(Serialize)]
#[client_packet("play:disconnect")]
pub struct CPlayDisconnect {
reason: TextComponent,
}
impl CPlayDisconnect {
pub fn new(reason: TextComponent) -> Self {
Self { reason }
}
}
#[derive(Deserialize)]
#[server_packet("login:move_player_pos")]
pub struct SPlayerPosition {
pub x: f64,
pub feet_y: f64,
pub z: f64,
pub ground: bool,
}
- 您还可以手动序列化/反序列化数据包,这在数据包更复杂时很有用。
-#[derive(Serialize)]
+ impl ClientPacket for CPlayDisconnect {
+ fn write(&self, bytebuf: &mut BytesMut) {
+ bytebuf.put_slice(&self.reason.encode());
+ }
-#[derive(Deserialize)]
+ impl ServerPacket for SPlayerPosition {
+ fn read(bytebuf: &mut Bytes) -> Result<Self, ReadingError> {
+ Ok(Self {
+ x: bytebuf.try_get_f64()?,
+ feet_y: bytebuf.try_get_f64()?,
+ z: bytebuf.try_get_f64()?,
+ ground: bytebuf.try_get_bool()?,
+ })
+ }
客户端
Pumpkin 将 Client
(客户端)和 Player
(玩家)分开分类。不在游戏状态的所有内容都是简单的 Client
。以下是它们的区别:
客户端
- 只能处于状态:Status、Login、Transfer、Config
- 不是活动实体
- 资源消耗小
玩家
- 只能处于 Play 状态
- 是世界中的一个活动实体
- 有更多数据并消耗更多资源
发送数据包
例子:
// 仅在 Status 状态下工作
client.send_packet(&CStatusResponse::new("{ description: "A Description"}"));
接收数据包
对于 Client
: src/client/mod.rs
// 将数据包放入正确的状态
fn handle_mystate_packet(
&self,
server: &Arc<Server>,
packet: &mut RawPacket,
) -> Result<(), ReadingError> {
let bytebuf = &mut packet.bytebuf;
match packet.id.0 {
SStatusRequest::PACKET_ID => {
self.handle_status_request(server, SStatusRequest::read(bytebuf)?)
.await;
}
+ MyPacket::PACKET_ID => {
+ self.handle_my_packet(MyPacket::read(bytebuf)?)
+ .await;
}
_ => {
log::error!(
"Failed to handle packet id {} while in ... state",
packet.id.0
);
}
};