Rede (Networking)
A maior parte do código de rede do Pumpkin pode ser encontrada na crate pumpkin-protocol.
Serverbound: Cliente→Servidor
Clientbound: Servidor→Cliente
Estrutura
Os pacotes no protocolo Pumpkin são organizados por funcionalidade e estado.
server
: Contém definições para pacotes serverbound.
client
: Contém definições para pacotes clientbound.
Estados
Handshake: Sempre o primeiro pacote enviado pelo cliente. Isso também determina o próximo estado, geralmente indicando se o jogador deseja realizar uma solicitação de status, entrar no servidor ou ser transferido.
Status: Indica que o cliente deseja ver uma resposta de status (MOTD).
Login: A sequência de login. Indica que o cliente deseja entrar no servidor.
Config: Uma sequência de pacotes de configuração que são principalmente enviados do servidor para o cliente (recursos, pacotes de recursos, links do servidor, etc.).
Play: O estado final, que indica que o jogador está pronto para entrar, também é usado para lidar com todos os outros pacotes de gameplay.
Protocolo Minecraft
Você pode encontrar todos os pacotes do Minecraft Java em Minecraft Wiki. Lá você também pode ver em qual estado eles estão. Você também pode ver todas as informações que os pacotes possuem, as quais podemos ler ou escrever, dependendo se são serverbound ou clientbound.
Adicionando um Pacote
- Adicionar um pacote é fácil. Primeiro, derive:
// Para pacotes clientbound:
#[derive(Serialize)]
// Para pacotes serverbound:
#[derive(Deserialize)]
- Em seguida, você deve deixar claro que sua struct representa um pacote. Isso automaticamente obterá o ID do pacote do arquivo JSON de pacotes.
// Para pacotes clientbound:
#[client_packet("play:disconnect")]
// Para pacotes serverbound:
#[server_packet("login:move_player_pos")]
- Agora você pode criar a
struct
.
IMPORTANTE
Por favor, comece o nome do pacote com "C" ou "S" para "Clientbound" ou "Serverbound". Além disso, se for um pacote que pode ser enviado em múltiplos estados, adicione o estado ao nome. Por exemplo, existem 3 pacotes de desconexão diferentes.
CLoginDisconnect
CConfigDisconnect
CPlayDisconnect
Crie campos dentro da sua estrutura de pacote para representar os dados que serão enviados.
IMPORTANTE
Use nomes de campos descritivos e tipos de dados apropriados.
Exemplos:
pub struct CPlayDisconnect {
reason: TextComponent,
// mais campos...
}
pub struct SPlayerPosition {
pub x: f64,
pub feet_y: f64,
pub z: f64,
pub ground: bool,
}
- (Apenas pacotes clientbound) Implemente uma função
new
para que possamos realmente criá-los inserindo os valores.
impl CPlayDisconnect {
pub fn new(reason: TextComponent) -> Self {
Self { reason }
}
}
- No final, tudo deve se juntar.
#[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,
}
- Você também pode serializar/deserializar o pacote manualmente, o que pode ser útil se o pacote for mais complexo.
-#[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()?,
+ })
+ }
- Agora você pode enviar o pacote clientbound (veja Enviando Pacotes) ou ouvir o pacote serverbound (veja Recebendo Pacotes).
Cliente
Pumpkin categoriza Client
s e Player
s separadamente. Tudo o que não está no estado de jogo é um simples Client
. Aqui estão as diferenças:
Client
- Só pode estar nos estados: Status, Login, Transfer, Config
- Não é uma entidade viva
- Consome poucos recursos
Player
- Só pode estar no estado Play
- É uma entidade viva no mundo
- Possui mais dados e consome mais recursos
Enviando Pacotes
Exemplo:
// Funciona apenas no estado Status
client.send_packet(&CStatusResponse::new("{ description: "A Description"}"));
Recebendo Pacotes
Para Client
s: src/client/mod.rs
// Coloque o pacote no estado correto
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!(
"Falha ao lidar com o pacote id {} enquanto no estado ...",
packet.id.0
);
}
};
Ok(())
}
Para Player
s: src/entity/player.rs
// Players só têm o estado Play
fn handle_play_packet(
&self,
server: &Arc<Server>,
packet: &mut RawPacket,
) -> Result<(), ReadingError> {
let bytebuf = &mut packet.bytebuf;
match packet.id.0 {
SChatMessage::PACKET_ID => {
self.handle_chat_message(SChatMessage::read(bytebuf)?).await;
}
MyPacket::PACKET_ID => {
+ self.handle_mypacket(server, MyPacket::read(bytebuf)?).await;
}
_ => {
log::error!(
"Falha ao lidar com o pacote id {} enquanto no estado ...",
packet.id.0
);
}
};
Ok(())
}
Compressão
Os pacotes do Minecraft podem usar compressão ZLib para decodificação/encodificação. Normalmente, existe um limite quando a compressão é aplicada; isso afeta principalmente pacotes de chunks.
Portabilidade
Para portar para uma nova versão do Minecraft, você pode comparar as diferenças no protocolo na referência de protocolo minecraft.wiki.
Além disso, altere o CURRENT_MC_PROTOCOL
em src/lib.rs
.