Refactor valence_protocol (#253)

## Description

- Remove duplicate packet definitions by using `Cow`.
- Rename packets to match yarn mappings.
- Remove some top-level re-exports.
- Move every packet into its own module for consistency.
- Move packet-specific types from the `types` module into the
appropriate packet module.
- Remove internal use of `EncodePacket`/`DecodePacket` derives and move
packet identification to `packet_group`. This can be done because there
are no duplicate packets anymore.
- Simplify some events.

In a future PR I plan to clean things up further by properly bounding
packet data (to prevent DoS exploits) and fixing any remaining
inconsistencies with the game's packet definitions.

## Test Plan

Behavior of `valence_protocol` should be the same.

Steps:
1. Use the packet inspector against the vanilla server to ensure packet
behavior has not changed.
2. Run the examples.
3. Run `valence_stresser`.
This commit is contained in:
Ryan Johnson 2023-02-23 22:16:22 -08:00 committed by GitHub
parent 9931c8a80b
commit 0960ad7ead
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
218 changed files with 3545 additions and 3118 deletions

View file

@ -15,15 +15,15 @@ use tokio::net::{TcpListener, TcpStream};
use tokio::sync::Semaphore;
use tokio::task::JoinHandle;
use tracing_subscriber::filter::LevelFilter;
use valence_protocol::packets::c2s::handshake::Handshake;
use valence_protocol::packets::c2s::login::{EncryptionResponse, LoginStart};
use valence_protocol::packets::c2s::play::C2sPlayPacket;
use valence_protocol::packets::c2s::status::{PingRequest, StatusRequest};
use valence_protocol::packets::s2c::login::{LoginSuccess, S2cLoginPacket};
use valence_protocol::packets::s2c::play::S2cPlayPacket;
use valence_protocol::packets::s2c::status::{PingResponse, StatusResponse};
use valence_protocol::types::HandshakeNextState;
use valence_protocol::{DecodePacket, EncodePacket, PacketDecoder, PacketEncoder};
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::packet::c2s::handshake::handshake::NextState;
use valence_protocol::packet::c2s::handshake::HandshakeC2s;
use valence_protocol::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s};
use valence_protocol::packet::c2s::status::{QueryPingC2s, QueryRequestC2s};
use valence_protocol::packet::s2c::login::LoginSuccessS2c;
use valence_protocol::packet::s2c::status::{QueryPongS2c, QueryResponseS2c};
use valence_protocol::packet::{C2sPlayPacket, S2cLoginPacket, S2cPlayPacket};
use valence_protocol::{DecodePacket, EncodePacket};
#[derive(Parser, Clone, Debug)]
#[clap(author, version, about)]
@ -179,23 +179,23 @@ async fn handle_connection(client: TcpStream, cli: Arc<Cli>) -> anyhow::Result<(
style: owo_colors::Style::new().green(),
};
let handshake: Handshake = c2s.rw_packet().await?;
let handshake: HandshakeC2s = c2s.rw_packet().await?;
match handshake.next_state {
HandshakeNextState::Status => {
c2s.rw_packet::<StatusRequest>().await?;
s2c.rw_packet::<StatusResponse>().await?;
c2s.rw_packet::<PingRequest>().await?;
s2c.rw_packet::<PingResponse>().await?;
NextState::Status => {
c2s.rw_packet::<QueryRequestC2s>().await?;
s2c.rw_packet::<QueryResponseS2c>().await?;
c2s.rw_packet::<QueryPingC2s>().await?;
s2c.rw_packet::<QueryPongS2c>().await?;
Ok(())
}
HandshakeNextState::Login => {
c2s.rw_packet::<LoginStart>().await?;
NextState::Login => {
c2s.rw_packet::<LoginHelloC2s>().await?;
match s2c.rw_packet::<S2cLoginPacket>().await? {
S2cLoginPacket::EncryptionRequest(_) => {
c2s.rw_packet::<EncryptionResponse>().await?;
S2cLoginPacket::LoginHelloS2c(_) => {
c2s.rw_packet::<LoginKeyC2s>().await?;
eprintln!(
"Encryption was enabled! Packet contents are inaccessible to the proxy. \
@ -207,7 +207,7 @@ async fn handle_connection(client: TcpStream, cli: Arc<Cli>) -> anyhow::Result<(
s2c_res = passthrough(s2c.read, s2c.write) => s2c_res,
};
}
S2cLoginPacket::SetCompression(pkt) => {
S2cLoginPacket::LoginCompressionS2c(pkt) => {
let threshold = pkt.threshold.0 as u32;
s2c.enc.set_compression(Some(threshold));
@ -215,12 +215,12 @@ async fn handle_connection(client: TcpStream, cli: Arc<Cli>) -> anyhow::Result<(
c2s.enc.set_compression(Some(threshold));
c2s.dec.set_compression(true);
s2c.rw_packet::<LoginSuccess>().await?;
s2c.rw_packet::<LoginSuccessS2c>().await?;
}
S2cLoginPacket::LoginSuccess(_) => {}
S2cLoginPacket::DisconnectLogin(_) => return Ok(()),
S2cLoginPacket::LoginPluginRequest(_) => {
bail!("got login plugin request. Don't know how to proceed.")
S2cLoginPacket::LoginSuccessS2c(_) => {}
S2cLoginPacket::LoginDisconnectS2c(_) => return Ok(()),
S2cLoginPacket::LoginQueryRequestS2c(_) => {
bail!("got login query request. Don't know how to proceed.")
}
}

View file

@ -1,5 +1,5 @@
use valence::client::despawn_disconnected_clients;
use valence::client::event::{default_event_handler, ChatMessage, UseItemOnBlock};
use valence::client::event::{default_event_handler, ChatMessage, PlayerInteractBlock};
use valence::prelude::*;
use valence_nbt::{compound, List};
use valence_protocol::types::Hand;
@ -75,7 +75,7 @@ fn init_clients(
fn event_handler(
clients: Query<&Client>,
mut messages: EventReader<ChatMessage>,
mut block_interacts: EventReader<UseItemOnBlock>,
mut block_interacts: EventReader<PlayerInteractBlock>,
mut instances: Query<&mut Instance>,
) {
let mut instance = instances.single_mut();
@ -93,7 +93,7 @@ fn event_handler(
nbt.insert("Text3", format!("~{}", client.username()).italic());
}
for UseItemOnBlock {
for PlayerInteractBlock {
client,
position,
hand,

View file

@ -1,6 +1,6 @@
use valence::client::despawn_disconnected_clients;
use valence::client::event::{
default_event_handler, FinishDigging, StartDigging, StartSneaking, UseItemOnBlock,
default_event_handler, PlayerInteractBlock, StartDigging, StartSneaking, StopDestroyBlock,
};
use valence::prelude::*;
use valence_protocol::types::Hand;
@ -93,7 +93,7 @@ fn digging_creative_mode(
fn digging_survival_mode(
clients: Query<&Client>,
mut instances: Query<&mut Instance>,
mut events: EventReader<FinishDigging>,
mut events: EventReader<StopDestroyBlock>,
) {
let mut instance = instances.single_mut();
@ -110,7 +110,7 @@ fn digging_survival_mode(
fn place_blocks(
mut clients: Query<(&Client, &mut Inventory)>,
mut instances: Query<&mut Instance>,
mut events: EventReader<UseItemOnBlock>,
mut events: EventReader<PlayerInteractBlock>,
) {
let mut instance = instances.single_mut();
@ -146,7 +146,7 @@ fn place_blocks(
};
inventory.replace_slot(slot_id, slot);
}
let real_pos = event.position.get_in_direction(event.face);
let real_pos = event.position.get_in_direction(event.direction);
instance.set_block(real_pos, block_kind.to_state());
}
}

View file

@ -1,7 +1,7 @@
use bevy_app::App;
use tracing::warn;
use valence::client::despawn_disconnected_clients;
use valence::client::event::{default_event_handler, ChatCommand, ChatMessage};
use valence::client::event::{default_event_handler, ChatMessage, CommandExecution};
use valence::prelude::*;
const SPAWN_Y: i32 = 64;
@ -73,7 +73,10 @@ fn handle_message_events(mut clients: Query<&mut Client>, mut messages: EventRea
}
}
fn handle_command_events(mut clients: Query<&mut Client>, mut commands: EventReader<ChatCommand>) {
fn handle_command_events(
mut clients: Query<&mut Client>,
mut commands: EventReader<CommandExecution>,
) {
for command in commands.iter() {
let Ok(mut client) = clients.get_component_mut::<Client>(command.client) else {
warn!("Unable to find client for message: {:?}", command);

View file

@ -1,6 +1,6 @@
use tracing::warn;
use valence::client::despawn_disconnected_clients;
use valence::client::event::{default_event_handler, StartSneaking, UseItemOnBlock};
use valence::client::event::{default_event_handler, PlayerInteractBlock, StartSneaking};
use valence::prelude::*;
const SPAWN_Y: i32 = 64;
@ -79,7 +79,7 @@ fn toggle_gamemode_on_sneak(
fn open_chest(
mut commands: Commands,
inventories: Query<Entity, (With<Inventory>, Without<Client>)>,
mut events: EventReader<UseItemOnBlock>,
mut events: EventReader<PlayerInteractBlock>,
) {
let Ok(inventory) = inventories.get_single() else {
warn!("No inventories");

View file

@ -1,7 +1,7 @@
use glam::Vec3Swizzles;
use valence::client::despawn_disconnected_clients;
use valence::client::event::{
default_event_handler, InteractWithEntity, StartSprinting, StopSprinting,
default_event_handler, PlayerInteract, StartSprinting, StopSprinting,
};
use valence::prelude::*;
@ -92,7 +92,7 @@ fn handle_combat_events(
server: Res<Server>,
mut start_sprinting: EventReader<StartSprinting>,
mut stop_sprinting: EventReader<StopSprinting>,
mut interact_with_entity: EventReader<InteractWithEntity>,
mut interact_with_entity: EventReader<PlayerInteract>,
mut clients: Query<(&mut Client, &mut CombatState, &mut McEntity)>,
) {
for &StartSprinting { client } in start_sprinting.iter() {
@ -107,7 +107,7 @@ fn handle_combat_events(
}
}
for &InteractWithEntity {
for &PlayerInteract {
client: attacker_client,
entity_id,
..

View file

@ -1,5 +1,5 @@
use valence::client::despawn_disconnected_clients;
use valence::client::event::{default_event_handler, ChatCommand};
use valence::client::event::{default_event_handler, CommandExecution};
use valence::prelude::*;
const SPAWN_Y: i32 = 64;
@ -51,7 +51,7 @@ fn init_clients(
}
}
fn interpret_command(mut clients: Query<&mut Client>, mut events: EventReader<ChatCommand>) {
fn interpret_command(mut clients: Query<&mut Client>, mut events: EventReader<CommandExecution>) {
for event in events.iter() {
let Ok(mut client) = clients.get_component_mut::<Client>(event.client) else {
continue;

View file

@ -6,9 +6,9 @@ use rand::Rng;
use valence::client::despawn_disconnected_clients;
use valence::client::event::default_event_handler;
use valence::prelude::*;
use valence_protocol::packets::s2c::play::SetTitleAnimationTimes;
use valence_protocol::packet::s2c::play::TitleFadeS2c;
use valence_protocol::sound::Sound;
use valence_protocol::types::SoundCategory;
use valence_protocol::Sound;
const START_POS: BlockPos = BlockPos::new(0, 100, 0);
const VIEW_DIST: u8 = 10;
@ -149,7 +149,7 @@ fn manage_blocks(mut clients: Query<(&mut Client, &mut GameState, &mut Instance)
client.set_title(
"",
state.score.to_string().color(Color::LIGHT_PURPLE).bold(),
SetTitleAnimationTimes {
TitleFadeS2c {
fade_in: 0,
stay: 7,
fade_out: 4,

View file

@ -1,7 +1,6 @@
use valence::client::despawn_disconnected_clients;
use valence::client::event::default_event_handler;
use valence::prelude::*;
use valence_protocol::packets::s2c::particle::Particle;
const SPAWN_Y: i32 = 64;

View file

@ -1,9 +1,9 @@
use valence::client::despawn_disconnected_clients;
use valence::client::event::{
default_event_handler, InteractWithEntity, ResourcePackStatus, ResourcePackStatusChange,
default_event_handler, PlayerInteract, ResourcePackStatus, ResourcePackStatusChange,
};
use valence::prelude::*;
use valence_protocol::types::EntityInteraction;
use valence_protocol::packet::c2s::play::player_interact::Interaction;
const SPAWN_Y: i32 = 64;
@ -59,12 +59,12 @@ fn init_clients(
}
}
fn prompt_on_punch(mut clients: Query<&mut Client>, mut events: EventReader<InteractWithEntity>) {
fn prompt_on_punch(mut clients: Query<&mut Client>, mut events: EventReader<PlayerInteract>) {
for event in events.iter() {
let Ok(mut client) = clients.get_mut(event.client) else {
continue;
};
if event.interact == EntityInteraction::Attack {
if event.interact == Interaction::Attack {
client.set_resource_pack(
"https://github.com/valence-rs/valence/raw/main/assets/example_pack.zip",
"d7c6108849fb190ec2a49f2d38b7f1f897d9ce9f",

View file

@ -9,21 +9,27 @@ use bytes::BytesMut;
use glam::{DVec3, Vec3};
use tracing::warn;
use uuid::Uuid;
use valence_protocol::packets::s2c::particle::Particle;
use valence_protocol::packets::s2c::play::{
AcknowledgeBlockChange, CombatDeath, DisconnectPlay, EntityEvent, GameEvent, KeepAliveS2c,
LoginPlay, ParticleS2c, PluginMessageS2c, RemoveEntitiesEncode, ResourcePackS2c, Respawn,
SetActionBarText, SetCenterChunk, SetDefaultSpawnPosition, SetEntityMetadata,
SetEntityVelocity, SetRenderDistance, SetSubtitleText, SetTitleAnimationTimes, SetTitleText,
SoundEffect, SynchronizePlayerPosition, SystemChatMessage, UnloadChunk,
};
use valence_protocol::types::{
GameEventKind, GameMode, GlobalPos, Property, SoundCategory, SyncPlayerPosLookFlags,
};
use valence_protocol::{
BlockPos, EncodePacket, Ident, ItemStack, PacketDecoder, PacketEncoder, RawBytes, Sound, Text,
Username, VarInt,
use valence_protocol::block_pos::BlockPos;
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::ident::Ident;
use valence_protocol::item::ItemStack;
use valence_protocol::packet::s2c::play::game_state_change::GameEventKind;
use valence_protocol::packet::s2c::play::particle::Particle;
use valence_protocol::packet::s2c::play::player_position_look::Flags as PlayerPositionLookFlags;
use valence_protocol::packet::s2c::play::{
ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, CustomPayloadS2c, DeathMessageS2c,
DisconnectS2c, EntitiesDestroyS2c, EntityStatusS2c, EntityTrackerUpdateS2c,
EntityVelocityUpdateS2c, GameJoinS2c, GameMessageS2c, GameStateChangeS2c, KeepAliveS2c,
OverlayMessageS2c, ParticleS2c, PlaySoundS2c, PlayerActionResponseS2c, PlayerPositionLookS2c,
PlayerRespawnS2c, PlayerSpawnPositionS2c, ResourcePackSendS2c, SubtitleS2c, TitleFadeS2c,
TitleS2c, UnloadChunkS2c,
};
use valence_protocol::sound::Sound;
use valence_protocol::text::Text;
use valence_protocol::types::{GameMode, GlobalPos, Property, SoundCategory};
use valence_protocol::username::Username;
use valence_protocol::var_int::VarInt;
use valence_protocol::EncodePacket;
use crate::dimension::DimensionId;
use crate::entity::data::Player;
@ -261,7 +267,7 @@ impl Client {
}
pub fn set_velocity(&mut self, velocity: impl Into<Vec3>) {
self.enc.write_packet(&SetEntityVelocity {
self.enc.write_packet(&EntityVelocityUpdateS2c {
entity_id: VarInt(0),
velocity: velocity_to_packet_units(velocity.into()),
});
@ -297,7 +303,7 @@ impl Client {
/// Kills the client and shows `message` on the death screen. If an entity
/// killed the player, you should supply it as `killer`.
pub fn kill(&mut self, killer: Option<&McEntity>, message: impl Into<Text>) {
self.write_packet(&CombatDeath {
self.write_packet(&DeathMessageS2c {
player_id: VarInt(0),
entity_id: killer.map_or(-1, |k| k.protocol_id()),
message: message.into().into(),
@ -306,7 +312,7 @@ impl Client {
/// Respawns client. Optionally can roll the credits before respawning.
pub fn win_game(&mut self, show_credits: bool) {
self.write_packet(&GameEvent {
self.write_packet(&GameStateChangeS2c {
kind: GameEventKind::WinGame,
value: if show_credits { 1.0 } else { 0.0 },
});
@ -322,7 +328,7 @@ impl Client {
self.has_respawn_screen = enable;
if !self.is_new {
self.write_packet(&GameEvent {
self.write_packet(&GameStateChangeS2c {
kind: GameEventKind::EnableRespawnScreen,
value: if enable { 0.0 } else { 1.0 },
});
@ -381,7 +387,7 @@ impl Client {
self.game_mode = game_mode;
if !self.is_new {
self.write_packet(&GameEvent {
self.write_packet(&GameStateChangeS2c {
kind: GameEventKind::ChangeGameMode,
value: game_mode as i32 as f32,
});
@ -397,7 +403,7 @@ impl Client {
return;
}
self.write_packet(&EntityEvent {
self.write_packet(&EntityStatusS2c {
entity_id: 0,
entity_status: 24 + op_level,
});
@ -421,7 +427,7 @@ impl Client {
}
pub fn trigger_status(&mut self, status: EntityStatus) {
self.write_packet(&EntityEvent {
self.write_packet(&EntityStatusS2c {
entity_id: 0,
entity_status: status as u8,
});
@ -457,16 +463,16 @@ impl Client {
/// Sends a system message to the player which is visible in the chat. The
/// message is only visible to this client.
pub fn send_message(&mut self, msg: impl Into<Text>) {
self.write_packet(&SystemChatMessage {
self.write_packet(&GameMessageS2c {
chat: msg.into().into(),
overlay: false,
});
}
pub fn send_plugin_message(&mut self, channel: Ident<&str>, data: &[u8]) {
self.write_packet(&PluginMessageS2c {
self.write_packet(&CustomPayloadS2c {
channel,
data: RawBytes(data),
data: data.into(),
});
}
@ -478,7 +484,7 @@ impl Client {
/// Kick the client with the given reason.
pub fn kick(&mut self, reason: impl Into<Text>) {
self.write_packet(&DisconnectPlay {
self.write_packet(&DisconnectS2c {
reason: reason.into().into(),
});
self.is_disconnected = true;
@ -501,7 +507,7 @@ impl Client {
forced: bool,
prompt_message: Option<Text>,
) {
self.write_packet(&ResourcePackS2c {
self.write_packet(&ResourcePackSendS2c {
url,
hash,
forced,
@ -513,21 +519,21 @@ impl Client {
///
/// A title is a large piece of text displayed in the center of the screen
/// which may also include a subtitle underneath it. The title can be
/// configured to fade in and out using the [`SetTitleAnimationTimes`]
/// configured to fade in and out using the [`TitleFadeS2c`]
/// struct.
pub fn set_title(
&mut self,
title: impl Into<Text>,
subtitle: impl Into<Text>,
animation: impl Into<Option<SetTitleAnimationTimes>>,
animation: impl Into<Option<TitleFadeS2c>>,
) {
let title = title.into().into();
let subtitle = subtitle.into();
self.write_packet(&SetTitleText { title_text: title });
self.write_packet(&TitleS2c { title_text: title });
if !subtitle.is_empty() {
self.write_packet(&SetSubtitleText {
self.write_packet(&SubtitleS2c {
subtitle_text: subtitle.into(),
});
}
@ -542,7 +548,7 @@ impl Client {
/// The action bar is a small piece of text displayed at the bottom of the
/// screen, above the hotbar.
pub fn set_action_bar(&mut self, text: impl Into<Text>) {
self.write_packet(&SetActionBarText {
self.write_packet(&OverlayMessageS2c {
action_bar_text: text.into().into(),
});
}
@ -552,7 +558,7 @@ impl Client {
/// If you want to show a particle effect to all players, use
/// [`Instance::play_particle`]
///
/// [`Instance::play_particle`]: crate::instance::Instance::play_particle
/// [`Instance::play_particle`]: Instance::play_particle
pub fn play_particle(
&mut self,
particle: &Particle,
@ -563,7 +569,7 @@ impl Client {
count: i32,
) {
self.write_packet(&ParticleS2c {
particle: particle.clone(),
particle: Cow::Borrowed(particle),
long_distance,
position: position.into().into(),
offset: offset.into().into(),
@ -577,7 +583,7 @@ impl Client {
/// If you want to play a sound effect to all players, use
/// [`Instance::play_sound`]
///
/// [`Instance::play_sound`]: crate::instance::Instance::play_sound
/// [`Instance::play_sound`]: Instance::play_sound
pub fn play_sound(
&mut self,
sound: Sound,
@ -588,7 +594,7 @@ impl Client {
) {
let position = position.into();
self.write_packet(&SoundEffect {
self.write_packet(&PlaySoundS2c {
id: sound.to_id(),
category,
position: (position * 8.0).as_ivec3().into(),
@ -638,7 +644,7 @@ pub(crate) fn update_clients(
&entities,
&server,
) {
client.write_packet(&DisconnectPlay {
client.write_packet(&DisconnectS2c {
reason: Text::from("").into(),
});
client.is_disconnected = true;
@ -689,7 +695,7 @@ fn update_one_client(
// The login packet is prepended so that it is sent before all the other
// packets. Some packets don't work correctly when sent before the login packet,
// which is why we're doing this.
client.enc.prepend_packet(&LoginPlay {
client.enc.prepend_packet(&GameJoinS2c {
entity_id: 0, // ID 0 is reserved for clients.
is_hardcore: client.is_hardcore,
game_mode: client.game_mode,
@ -718,7 +724,7 @@ fn update_one_client(
} else {
if client.view_distance != client.old_view_distance {
// Change the render distance fog.
client.enc.append_packet(&SetRenderDistance {
client.enc.append_packet(&ChunkLoadDistanceS2c {
view_distance: VarInt(client.view_distance.into()),
})?;
}
@ -733,7 +739,7 @@ fn update_one_client(
position: pos,
});
client.enc.append_packet(&Respawn {
client.enc.append_packet(&PlayerRespawnS2c {
dimension_type_name: dimension_name,
dimension_name,
hashed_seed: 0,
@ -770,7 +776,7 @@ fn update_one_client(
// Make sure the center chunk is set before loading chunks!
if old_view.pos != view.pos {
// TODO: does the client initialize the center chunk to (0, 0)?
client.enc.write_packet(&SetCenterChunk {
client.enc.write_packet(&ChunkRenderDistanceCenterS2c {
chunk_x: VarInt(view.pos.x),
chunk_z: VarInt(view.pos.z),
});
@ -782,7 +788,7 @@ fn update_one_client(
if let Some(cell) = old_instance.partition.get(&pos) {
if cell.chunk_removed && cell.chunk.is_none() {
// Chunk was previously loaded and is now deleted.
client.enc.write_packet(&UnloadChunk {
client.enc.write_packet(&UnloadChunkS2c {
chunk_x: pos.x,
chunk_z: pos.z,
});
@ -841,7 +847,7 @@ fn update_one_client(
if let Some(cell) = old_instance.partition.get(&pos) {
// Unload the chunk at this cell if it was loaded.
if cell.chunk.is_some() {
client.enc.write_packet(&UnloadChunk {
client.enc.write_packet(&UnloadChunkS2c {
chunk_x: pos.x,
chunk_z: pos.z,
});
@ -896,7 +902,7 @@ fn update_one_client(
if let Some(cell) = instance.partition.get(&pos) {
// Unload the chunk at this cell if it was loaded.
if cell.chunk.is_some() {
client.enc.write_packet(&UnloadChunk {
client.enc.write_packet(&UnloadChunkS2c {
chunk_x: pos.x,
chunk_z: pos.z,
});
@ -943,8 +949,8 @@ fn update_one_client(
// Despawn all the entities that are queued to be despawned.
if !client.entities_to_despawn.is_empty() {
client.enc.append_packet(&RemoveEntitiesEncode {
entity_ids: &client.entities_to_despawn,
client.enc.append_packet(&EntitiesDestroyS2c {
entity_ids: Cow::Borrowed(&client.entities_to_despawn),
})?;
client.entities_to_despawn.clear();
@ -953,14 +959,14 @@ fn update_one_client(
// Teleport the client. Do this after chunk packets are sent so the client does
// not accidentally pass through blocks.
if client.position_modified || client.yaw_modified || client.pitch_modified {
let flags = SyncPlayerPosLookFlags::new()
let flags = PlayerPositionLookFlags::new()
.with_x(!client.position_modified)
.with_y(!client.position_modified)
.with_z(!client.position_modified)
.with_y_rot(!client.yaw_modified)
.with_x_rot(!client.pitch_modified);
client.enc.write_packet(&SynchronizePlayerPosition {
client.enc.write_packet(&PlayerPositionLookS2c {
position: if client.position_modified {
client.position.to_array()
} else {
@ -988,7 +994,7 @@ fn update_one_client(
// This closes the "downloading terrain" screen.
// Send this after the initial chunks are loaded.
if client.is_new {
client.enc.write_packet(&SetDefaultSpawnPosition {
client.enc.write_packet(&PlayerSpawnPositionS2c {
position: BlockPos::at(client.position),
angle: client.yaw,
});
@ -1002,15 +1008,15 @@ fn update_one_client(
client.scratch.push(0xff);
client.enc.write_packet(&SetEntityMetadata {
client.enc.write_packet(&EntityTrackerUpdateS2c {
entity_id: VarInt(0),
metadata: RawBytes(&client.scratch),
metadata: client.scratch.as_slice().into(),
});
}
// Acknowledge broken/placed blocks.
if client.block_change_sequence != 0 {
client.enc.write_packet(&AcknowledgeBlockChange {
client.enc.write_packet(&PlayerActionResponseS2c {
sequence: VarInt(client.block_change_sequence),
});
@ -1034,8 +1040,8 @@ mod tests {
use std::collections::BTreeSet;
use bevy_app::App;
use valence_protocol::packets::s2c::play::ChunkDataAndUpdateLight;
use valence_protocol::packets::S2cPlayPacket;
use valence_protocol::packet::s2c::play::ChunkDataS2c;
use valence_protocol::packet::S2cPlayPacket;
use super::*;
use crate::instance::Chunk;
@ -1070,10 +1076,8 @@ mod tests {
let mut loaded_chunks = BTreeSet::new();
for pkt in client_helper.collect_sent().unwrap() {
if let S2cPlayPacket::ChunkDataAndUpdateLight(ChunkDataAndUpdateLight {
chunk_x,
chunk_z,
..
if let S2cPlayPacket::ChunkDataS2c(ChunkDataS2c {
chunk_x, chunk_z, ..
}) = pkt
{
assert!(
@ -1098,17 +1102,15 @@ mod tests {
for pkt in client_helper.collect_sent().unwrap() {
match pkt {
S2cPlayPacket::ChunkDataAndUpdateLight(ChunkDataAndUpdateLight {
chunk_x,
chunk_z,
..
S2cPlayPacket::ChunkDataS2c(ChunkDataS2c {
chunk_x, chunk_z, ..
}) => {
assert!(
loaded_chunks.insert(ChunkPos::new(chunk_x, chunk_z)),
"({chunk_x}, {chunk_z})"
);
}
S2cPlayPacket::UnloadChunk(UnloadChunk { chunk_x, chunk_z }) => {
S2cPlayPacket::UnloadChunkS2c(UnloadChunkS2c { chunk_x, chunk_z }) => {
assert!(
loaded_chunks.remove(&ChunkPos::new(chunk_x, chunk_z)),
"({chunk_x}, {chunk_z})"

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,8 @@ use serde::Serialize;
use tokio::runtime::Handle;
use tracing::error;
use uuid::Uuid;
use valence_protocol::{Text, Username};
use valence_protocol::text::Text;
use valence_protocol::username::Username;
use crate::biome::Biome;
use crate::dimension::Dimension;

View file

@ -9,13 +9,14 @@ use glam::{DVec3, UVec3, Vec3};
use rustc_hash::FxHashMap;
use tracing::warn;
use uuid::Uuid;
use valence_protocol::entity_meta::{Facing, PaintingKind, Pose};
use valence_protocol::packets::s2c::play::{
EntityAnimationS2c, EntityEvent as EntityEventS2c, SetEntityMetadata, SetEntityVelocity,
SetHeadRotation, SpawnEntity, SpawnExperienceOrb, SpawnPlayer, TeleportEntity,
UpdateEntityPosition, UpdateEntityPositionAndRotation, UpdateEntityRotation,
use valence_protocol::byte_angle::ByteAngle;
use valence_protocol::packet::s2c::play::{
EntityAnimationS2c, EntityPositionS2c, EntitySetHeadYawS2c, EntitySpawnS2c,
EntityStatusS2c as EntityEventS2c, EntityTrackerUpdateS2c, EntityVelocityUpdateS2c,
ExperienceOrbSpawnS2c, MoveRelativeS2c, PlayerSpawnS2c, RotateAndMoveRelativeS2c, RotateS2c,
};
use valence_protocol::{ByteAngle, RawBytes, VarInt};
use valence_protocol::tracked_data::{Facing, PaintingKind, Pose};
use valence_protocol::var_int::VarInt;
use crate::config::DEFAULT_TPS;
use crate::math::Aabb;
@ -321,7 +322,7 @@ impl McEntity {
/// The hitbox of an entity is determined by its position, entity type, and
/// other state specific to that type.
///
/// [interact event]: crate::client::event::InteractWithEntity
/// [interact event]: crate::client::event::PlayerInteract
pub fn hitbox(&self) -> Aabb {
fn baby(is_baby: bool, adult_hitbox: [f64; 3]) -> [f64; 3] {
if is_baby {
@ -612,7 +613,7 @@ impl McEntity {
position: DVec3,
scratch: &mut Vec<u8>,
) {
let with_object_data = |data| SpawnEntity {
let with_object_data = |data| EntitySpawnS2c {
entity_id: VarInt(self.protocol_id),
object_uuid: self.uuid,
kind: VarInt(self.kind() as i32),
@ -626,13 +627,13 @@ impl McEntity {
match &self.data {
TrackedData::Marker(_) => {}
TrackedData::ExperienceOrb(_) => writer.write_packet(&SpawnExperienceOrb {
TrackedData::ExperienceOrb(_) => writer.write_packet(&ExperienceOrbSpawnS2c {
entity_id: VarInt(self.protocol_id),
position: position.to_array(),
count: 0, // TODO
}),
TrackedData::Player(_) => {
writer.write_packet(&SpawnPlayer {
writer.write_packet(&PlayerSpawnS2c {
entity_id: VarInt(self.protocol_id),
player_uuid: self.uuid,
position: position.to_array(),
@ -641,7 +642,7 @@ impl McEntity {
});
// Player spawn packet doesn't include head yaw for some reason.
writer.write_packet(&SetHeadRotation {
writer.write_packet(&EntitySetHeadYawS2c {
entity_id: VarInt(self.protocol_id),
head_yaw: ByteAngle::from_degrees(self.head_yaw),
});
@ -673,9 +674,9 @@ impl McEntity {
scratch.clear();
self.data.write_initial_tracked_data(scratch);
if !scratch.is_empty() {
writer.write_packet(&SetEntityMetadata {
writer.write_packet(&EntityTrackerUpdateS2c {
entity_id: VarInt(self.protocol_id),
metadata: RawBytes(scratch),
metadata: scratch.as_slice().into(),
});
}
}
@ -690,7 +691,7 @@ impl McEntity {
let changed_position = self.position != self.old_position;
if changed_position && !needs_teleport && self.yaw_or_pitch_modified {
writer.write_packet(&UpdateEntityPositionAndRotation {
writer.write_packet(&RotateAndMoveRelativeS2c {
entity_id,
delta: (position_delta * 4096.0).to_array().map(|v| v as i16),
yaw: ByteAngle::from_degrees(self.yaw),
@ -699,7 +700,7 @@ impl McEntity {
});
} else {
if changed_position && !needs_teleport {
writer.write_packet(&UpdateEntityPosition {
writer.write_packet(&MoveRelativeS2c {
entity_id,
delta: (position_delta * 4096.0).to_array().map(|v| v as i16),
on_ground: self.on_ground,
@ -707,7 +708,7 @@ impl McEntity {
}
if self.yaw_or_pitch_modified {
writer.write_packet(&UpdateEntityRotation {
writer.write_packet(&RotateS2c {
entity_id,
yaw: ByteAngle::from_degrees(self.yaw),
pitch: ByteAngle::from_degrees(self.pitch),
@ -717,7 +718,7 @@ impl McEntity {
}
if needs_teleport {
writer.write_packet(&TeleportEntity {
writer.write_packet(&EntityPositionS2c {
entity_id,
position: self.position.to_array(),
yaw: ByteAngle::from_degrees(self.yaw),
@ -727,14 +728,14 @@ impl McEntity {
}
if self.velocity_modified {
writer.write_packet(&SetEntityVelocity {
writer.write_packet(&EntityVelocityUpdateS2c {
entity_id,
velocity: velocity_to_packet_units(self.velocity),
});
}
if self.head_yaw_modified {
writer.write_packet(&SetHeadRotation {
writer.write_packet(&EntitySetHeadYawS2c {
entity_id,
head_yaw: ByteAngle::from_degrees(self.head_yaw),
});
@ -743,9 +744,9 @@ impl McEntity {
scratch.clear();
self.data.write_updated_tracked_data(scratch);
if !scratch.is_empty() {
writer.write_packet(&SetEntityMetadata {
writer.write_packet(&EntityTrackerUpdateS2c {
entity_id,
metadata: RawBytes(scratch),
metadata: scratch.as_slice().into(),
});
}

View file

@ -4,7 +4,11 @@
#![allow(clippy::all, missing_docs, trivial_numeric_casts, dead_code)]
use uuid::Uuid;
use valence_protocol::entity_meta::*;
use valence_protocol::{BlockPos, BlockState, Encode, Text, VarInt};
use valence_protocol::block::BlockState;
use valence_protocol::block_pos::BlockPos;
use valence_protocol::text::Text;
use valence_protocol::tracked_data::*;
use valence_protocol::var_int::VarInt;
use valence_protocol::Encode;
include!(concat!(env!("OUT_DIR"), "/entity.rs"));

View file

@ -1,20 +1,25 @@
use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::collections::BTreeSet;
use std::iter::FusedIterator;
use bevy_ecs::prelude::*;
pub use chunk::{Block, BlockEntity, BlockMut, BlockRef, Chunk};
pub use chunk_entry::*;
use glam::{DVec3, Vec3};
use num::integer::div_ceil;
use rustc_hash::FxHashMap;
use valence_protocol::packets::s2c::particle::{Particle, ParticleS2c};
use valence_protocol::packets::s2c::play::{SetActionBarText, SoundEffect};
use valence_protocol::array::LengthPrefixedArray;
use valence_protocol::block_pos::BlockPos;
use valence_protocol::packet::s2c::play::particle::Particle;
use valence_protocol::packet::s2c::play::{OverlayMessageS2c, ParticleS2c, PlaySoundS2c};
use valence_protocol::sound::Sound;
use valence_protocol::text::Text;
use valence_protocol::types::SoundCategory;
use valence_protocol::{BlockPos, EncodePacket, LengthPrefixedArray, Sound, Text};
use valence_protocol::EncodePacket;
use crate::dimension::DimensionId;
use crate::entity::McEntity;
pub use crate::instance::chunk::{Block, BlockMut, BlockRef, Chunk};
use crate::packet::{PacketWriter, WritePacket};
use crate::server::{Server, SharedServer};
use crate::view::ChunkPos;
@ -383,7 +388,7 @@ impl Instance {
self.write_packet_at(
&ParticleS2c {
particle: particle.clone(),
particle: Cow::Borrowed(particle),
long_distance,
position: position.into(),
offset: offset.into().into(),
@ -408,7 +413,7 @@ impl Instance {
let position = position.into();
self.write_packet_at(
&SoundEffect {
&PlaySoundS2c {
id: sound.to_id(),
category,
position: (position * 8.0).as_ivec3().into(),
@ -422,7 +427,7 @@ impl Instance {
/// Sets the action bar text of all players in the instance.
pub fn set_action_bar(&mut self, text: impl Into<Text>) {
self.write_packet(&SetActionBarText {
self.write_packet(&OverlayMessageS2c {
action_bar_text: text.into().into(),
});
}

View file

@ -6,12 +6,15 @@ use std::sync::atomic::{AtomicBool, Ordering};
// Using nonstandard mutex to avoid poisoning API.
use parking_lot::Mutex;
use valence_nbt::{compound, Compound};
use valence_protocol::block::{BlockEntity, BlockState};
use valence_protocol::packets::s2c::play::{
BlockEntityData, BlockUpdate, ChunkDataAndUpdateLightEncode, UpdateSectionBlocksEncode,
use valence_protocol::block::{BlockEntityKind, BlockState};
use valence_protocol::block_pos::BlockPos;
use valence_protocol::packet::s2c::play::chunk_data::ChunkDataBlockEntity;
use valence_protocol::packet::s2c::play::{
BlockEntityUpdateS2c, BlockUpdateS2c, ChunkDataS2c, ChunkDeltaUpdateS2c,
};
use valence_protocol::types::ChunkDataBlockEntity;
use valence_protocol::{BlockPos, Encode, VarInt, VarLong};
use valence_protocol::var_int::VarInt;
use valence_protocol::var_long::VarLong;
use valence_protocol::Encode;
use crate::biome::BiomeId;
use crate::instance::paletted_container::PalettedContainer;
@ -171,6 +174,18 @@ impl<'a> BlockMut<'a> {
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BlockEntity {
pub kind: BlockEntityKind,
pub nbt: Compound,
}
impl BlockEntity {
pub fn new(kind: BlockEntityKind, nbt: Compound) -> Self {
Self { kind, nbt }
}
}
const SECTION_BLOCK_COUNT: usize = 16 * 16 * 16;
const SECTION_BIOME_COUNT: usize = 4 * 4 * 4;
@ -329,7 +344,7 @@ impl Chunk<true> {
let global_y = info.min_y + sect_y as i32 * 16 + offset_y as i32;
let global_z = pos.z * 16 + offset_z as i32;
writer.write_packet(&BlockUpdate {
writer.write_packet(&BlockUpdateS2c {
position: BlockPos::new(global_x, global_y, global_z),
block_id: VarInt(block as i32),
})
@ -338,10 +353,10 @@ impl Chunk<true> {
| (pos.z as i64 & 0x3fffff) << 20
| (sect_y as i64 + info.min_y.div_euclid(16) as i64) & 0xfffff;
writer.write_packet(&UpdateSectionBlocksEncode {
writer.write_packet(&ChunkDeltaUpdateS2c {
chunk_section_position,
invert_trust_edges: false,
blocks: &sect.section_updates,
blocks: Cow::Borrowed(&sect.section_updates),
});
}
}
@ -357,7 +372,7 @@ impl Chunk<true> {
let global_y = info.min_y + y as i32;
let global_z = pos.z * 16 + z as i32;
writer.write_packet(&BlockEntityData {
writer.write_packet(&BlockEntityUpdateS2c {
position: BlockPos::new(global_x, global_y, global_z),
kind: block_entity.kind,
data: Cow::Borrowed(&block_entity.nbt),
@ -429,21 +444,23 @@ impl Chunk<true> {
})
.collect();
writer.write_packet(&ChunkDataAndUpdateLightEncode {
let heightmaps = compound! {
// TODO: MOTION_BLOCKING heightmap
};
writer.write_packet(&ChunkDataS2c {
chunk_x: pos.x,
chunk_z: pos.z,
heightmaps: &compound! {
// TODO: MOTION_BLOCKING heightmap
},
heightmaps: Cow::Owned(heightmaps),
blocks_and_biomes: scratch,
block_entities: &block_entities,
block_entities: Cow::Borrowed(&block_entities),
trust_edges: true,
sky_light_mask: &info.filler_sky_light_mask,
block_light_mask: &[],
empty_sky_light_mask: &[],
empty_block_light_mask: &[],
sky_light_arrays: &info.filler_sky_light_arrays,
block_light_arrays: &[],
sky_light_mask: Cow::Borrowed(&info.filler_sky_light_mask),
block_light_mask: Cow::Borrowed(&[]),
empty_sky_light_mask: Cow::Borrowed(&[]),
empty_block_light_mask: Cow::Borrowed(&[]),
sky_light_arrays: Cow::Borrowed(&info.filler_sky_light_arrays),
block_light_arrays: Cow::Borrowed(&[]),
});
}

View file

@ -2,7 +2,8 @@ use std::array;
use std::io::Write;
use arrayvec::ArrayVec;
use valence_protocol::{Encode, VarInt};
use valence_protocol::var_int::VarInt;
use valence_protocol::Encode;
use crate::math::bit_width;

View file

@ -1,14 +1,19 @@
use std::borrow::Cow;
use std::iter::FusedIterator;
use bevy_ecs::prelude::*;
use tracing::{debug, warn};
use valence_protocol::packets::s2c::play::{
CloseContainerS2c, OpenScreen, SetContainerContentEncode, SetContainerSlotEncode,
use valence_protocol::item::ItemStack;
use valence_protocol::packet::s2c::play::{
CloseScreenS2c, InventoryS2c, OpenScreenS2c, ScreenHandlerSlotUpdateS2c,
};
use valence_protocol::text::Text;
use valence_protocol::types::{GameMode, WindowType};
use valence_protocol::{ItemStack, Text, VarInt};
use valence_protocol::var_int::VarInt;
use crate::client::event::{ClickContainer, CloseContainer, SetCreativeModeSlot, SetHeldItem};
use crate::client::event::{
ClickSlot, CloseHandledScreen, CreativeInventoryAction, UpdateSelectedSlot,
};
use crate::client::Client;
#[derive(Debug, Clone, Component)]
@ -124,11 +129,11 @@ pub(crate) fn update_player_inventories(
client.inventory_state_id += 1;
let cursor_item = client.cursor_item.clone();
let state_id = client.inventory_state_id.0;
client.write_packet(&SetContainerContentEncode {
client.write_packet(&InventoryS2c {
window_id: 0,
state_id: VarInt(state_id),
slots: inventory.slot_slice(),
carried_item: &cursor_item,
slots: Cow::Borrowed(inventory.slot_slice()),
carried_item: Cow::Borrowed(&cursor_item),
});
client.cursor_item_modified = false;
@ -142,11 +147,11 @@ pub(crate) fn update_player_inventories(
let state_id = client.inventory_state_id.0;
for (i, slot) in inventory.slots.iter().enumerate() {
if ((modified_filtered >> i) & 1) == 1 {
client.write_packet(&SetContainerSlotEncode {
client.write_packet(&ScreenHandlerSlotUpdateS2c {
window_id: 0,
state_id: VarInt(state_id),
slot_idx: i as i16,
slot_data: slot.as_ref(),
slot_data: Cow::Borrowed(slot),
});
}
}
@ -162,13 +167,14 @@ pub(crate) fn update_player_inventories(
client.cursor_item_modified = false;
// TODO: eliminate clone?
let cursor_item = client.cursor_item.clone();
let state_id = client.inventory_state_id.0;
client.write_packet(&SetContainerSlotEncode {
client.write_packet(&ScreenHandlerSlotUpdateS2c {
window_id: -1,
state_id: VarInt(state_id),
slot_idx: -1,
slot_data: cursor_item.as_ref(),
slot_data: Cow::Borrowed(&cursor_item),
});
}
}
@ -342,7 +348,7 @@ pub(crate) fn update_open_inventories(
// the inventory no longer exists, so close the inventory
commands.entity(client_entity).remove::<OpenInventory>();
let window_id = client.window_id;
client.write_packet(&CloseContainerS2c {
client.write_packet(&CloseScreenS2c {
window_id,
});
continue;
@ -353,18 +359,19 @@ pub(crate) fn update_open_inventories(
client.window_id = client.window_id % 100 + 1;
open_inventory.client_modified = 0;
let packet = OpenScreen {
let packet = OpenScreenS2c {
window_id: VarInt(client.window_id.into()),
window_type: WindowType::from(inventory.kind),
window_title: (&inventory.title).into(),
};
client.write_packet(&packet);
let packet = SetContainerContentEncode {
let packet = InventoryS2c {
window_id: client.window_id,
state_id: VarInt(client.inventory_state_id.0),
slots: inventory.slot_slice(),
carried_item: &client.cursor_item.clone(),
slots: Cow::Borrowed(inventory.slot_slice()),
// TODO: eliminate clone?
carried_item: Cow::Owned(client.cursor_item.clone()),
};
client.write_packet(&packet);
} else {
@ -372,11 +379,12 @@ pub(crate) fn update_open_inventories(
if inventory.modified == u64::MAX {
// send the entire inventory
client.inventory_state_id += 1;
let packet = SetContainerContentEncode {
let packet = InventoryS2c {
window_id: client.window_id,
state_id: VarInt(client.inventory_state_id.0),
slots: inventory.slot_slice(),
carried_item: &client.cursor_item.clone(),
slots: Cow::Borrowed(inventory.slot_slice()),
// TODO: eliminate clone?
carried_item: Cow::Owned(client.cursor_item.clone()),
};
client.write_packet(&packet);
} else {
@ -389,11 +397,11 @@ pub(crate) fn update_open_inventories(
let state_id = client.inventory_state_id.0;
for (i, slot) in inventory.slots.iter().enumerate() {
if (modified_filtered >> i) & 1 == 1 {
client.write_packet(&SetContainerSlotEncode {
client.write_packet(&ScreenHandlerSlotUpdateS2c {
window_id,
state_id: VarInt(state_id),
slot_idx: i as i16,
slot_data: slot.as_ref(),
slot_data: Cow::Borrowed(slot),
});
}
}
@ -418,7 +426,7 @@ pub(crate) fn update_open_inventories(
/// Handles clients telling the server that they are closing an inventory.
pub(crate) fn handle_close_container(
mut commands: Commands,
mut events: EventReader<CloseContainer>,
mut events: EventReader<CloseHandledScreen>,
) {
for event in events.iter() {
commands.entity(event.client).remove::<OpenInventory>();
@ -434,7 +442,7 @@ pub(crate) fn update_client_on_close_inventory(
for entity in removals.iter() {
if let Ok(mut client) = clients.get_component_mut::<Client>(entity) {
let window_id = client.window_id;
client.write_packet(&CloseContainerS2c { window_id });
client.write_packet(&CloseScreenS2c { window_id });
}
}
}
@ -442,7 +450,7 @@ pub(crate) fn update_client_on_close_inventory(
pub(crate) fn handle_click_container(
mut clients: Query<(&mut Client, &mut Inventory, Option<&mut OpenInventory>)>,
mut inventories: Query<&mut Inventory, Without<Client>>,
mut events: EventReader<ClickContainer>,
mut events: EventReader<ClickSlot>,
) {
for event in events.iter() {
let Ok((mut client, mut client_inventory, mut open_inventory)) =
@ -472,11 +480,12 @@ pub(crate) fn handle_click_container(
// client is out of sync, resync, ignore click
debug!("Client state id mismatch, resyncing");
client.inventory_state_id += 1;
let packet = SetContainerContentEncode {
let packet = InventoryS2c {
window_id: client.window_id,
state_id: VarInt(client.inventory_state_id.0),
slots: target_inventory.slot_slice(),
carried_item: &client.cursor_item.clone(),
slots: Cow::Borrowed(target_inventory.slot_slice()),
// TODO: eliminate clone?
carried_item: Cow::Owned(client.cursor_item.clone()),
};
client.write_packet(&packet);
continue;
@ -484,15 +493,15 @@ pub(crate) fn handle_click_container(
client.cursor_item = event.carried_item.clone();
for (slot_id, item) in event.slot_changes.clone() {
if (0i16..target_inventory.slot_count() as i16).contains(&slot_id) {
for slot in event.slot_changes.clone() {
if (0i16..target_inventory.slot_count() as i16).contains(&slot.idx) {
// the client is interacting with a slot in the target inventory
target_inventory.replace_slot(slot_id as u16, item);
open_inventory.client_modified |= 1 << slot_id;
target_inventory.replace_slot(slot.idx as u16, slot.item);
open_inventory.client_modified |= 1 << slot.idx;
} else {
// the client is interacting with a slot in their own inventory
let slot_id = convert_to_player_slot_id(target_inventory.kind, slot_id as u16);
client_inventory.replace_slot(slot_id, item);
let slot_id = convert_to_player_slot_id(target_inventory.kind, slot.idx as u16);
client_inventory.replace_slot(slot_id, slot.item);
client.inventory_slots_modified |= 1 << slot_id;
}
}
@ -503,11 +512,12 @@ pub(crate) fn handle_click_container(
// client is out of sync, resync, and ignore the click
debug!("Client state id mismatch, resyncing");
client.inventory_state_id += 1;
let packet = SetContainerContentEncode {
let packet = InventoryS2c {
window_id: client.window_id,
state_id: VarInt(client.inventory_state_id.0),
slots: client_inventory.slot_slice(),
carried_item: &client.cursor_item.clone(),
slots: Cow::Borrowed(client_inventory.slot_slice()),
// TODO: eliminate clone?
carried_item: Cow::Owned(client.cursor_item.clone()),
};
client.write_packet(&packet);
continue;
@ -515,16 +525,16 @@ pub(crate) fn handle_click_container(
// TODO: do more validation on the click
client.cursor_item = event.carried_item.clone();
for (slot_id, item) in event.slot_changes.clone() {
if (0i16..client_inventory.slot_count() as i16).contains(&slot_id) {
client_inventory.replace_slot(slot_id as u16, item);
client.inventory_slots_modified |= 1 << slot_id;
for slot in event.slot_changes.clone() {
if (0i16..client_inventory.slot_count() as i16).contains(&slot.idx) {
client_inventory.replace_slot(slot.idx as u16, slot.item);
client.inventory_slots_modified |= 1 << slot.idx;
} else {
// the client is trying to interact with a slot that does not exist,
// ignore
warn!(
"Client attempted to interact with slot {} which does not exist",
slot_id
slot.idx
);
}
}
@ -534,7 +544,7 @@ pub(crate) fn handle_click_container(
pub(crate) fn handle_set_slot_creative(
mut clients: Query<(&mut Client, &mut Inventory)>,
mut events: EventReader<SetCreativeModeSlot>,
mut events: EventReader<CreativeInventoryAction>,
) {
for event in events.iter() {
if let Ok((mut client, mut inventory)) = clients.get_mut(event.client) {
@ -554,11 +564,11 @@ pub(crate) fn handle_set_slot_creative(
// creative mode Simply marking the slot as modified is not enough. This was
// discovered because shift-clicking the destroy item slot in creative mode does
// not work without this hack.
client.write_packet(&SetContainerSlotEncode {
client.write_packet(&ScreenHandlerSlotUpdateS2c {
window_id: 0,
state_id: VarInt(state_id),
slot_idx: event.slot,
slot_data: event.clicked_item.as_ref(),
slot_data: Cow::Borrowed(&event.clicked_item),
});
}
}
@ -566,7 +576,7 @@ pub(crate) fn handle_set_slot_creative(
pub(crate) fn handle_set_held_item(
mut clients: Query<&mut Client>,
mut events: EventReader<SetHeldItem>,
mut events: EventReader<UpdateSelectedSlot>,
) {
for event in events.iter() {
if let Ok(mut client) = clients.get_mut(event.client) {
@ -590,8 +600,8 @@ fn convert_hotbar_slot_id(slot_id: u16) -> u16 {
#[cfg(test)]
mod test {
use bevy_app::App;
use valence_protocol::packets::S2cPlayPacket;
use valence_protocol::ItemKind;
use valence_protocol::item::ItemKind;
use valence_protocol::packet::S2cPlayPacket;
use super::*;
use crate::unit_test::util::scenario_single_client;
@ -636,12 +646,12 @@ mod test {
// Make assertions
let sent_packets = client_helper.collect_sent()?;
assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreen(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::SetContainerContent(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreenS2c(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_));
assert_packet_order!(
sent_packets,
S2cPlayPacket::OpenScreen(_),
S2cPlayPacket::SetContainerContent(_)
S2cPlayPacket::OpenScreenS2c(_),
S2cPlayPacket::InventoryS2c(_)
);
Ok(())
@ -680,7 +690,7 @@ mod test {
// Make assertions
let sent_packets = client_helper.collect_sent()?;
assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseContainerS2c(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseScreenS2c(_));
Ok(())
}
@ -715,13 +725,13 @@ mod test {
// Make assertions
assert!(app.world.get::<OpenInventory>(client_ent).is_none());
let sent_packets = client_helper.collect_sent()?;
assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseContainerS2c(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseScreenS2c(_));
Ok(())
}
#[test]
fn test_should_modify_player_inventory_click_container() -> anyhow::Result<()> {
fn test_should_modify_player_inventory_click_slot() -> anyhow::Result<()> {
let mut app = App::new();
let (client_ent, mut client_helper) = scenario_single_client(&mut app);
let mut inventory = app
@ -740,13 +750,16 @@ mod test {
.get::<Client>(client_ent)
.unwrap()
.inventory_state_id;
client_helper.send(&valence_protocol::packets::c2s::play::ClickContainer {
client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s {
window_id: 0,
button: 0,
mode: valence_protocol::types::ClickContainerMode::Click,
mode: valence_protocol::packet::c2s::play::click_slot::ClickMode::Click,
state_id: VarInt(state_id.0),
slot_idx: 20,
slots: vec![(20, None)],
slots: vec![valence_protocol::packet::c2s::play::click_slot::Slot {
idx: 20,
item: None,
}],
carried_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)),
});
@ -761,7 +774,7 @@ mod test {
assert_packet_count!(
sent_packets,
0,
S2cPlayPacket::SetContainerContent(_) | S2cPlayPacket::SetContainerSlot(_)
S2cPlayPacket::InventoryS2c(_) | S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
let inventory = app
.world
@ -807,7 +820,11 @@ mod test {
let sent_packets = client_helper.collect_sent()?;
// because the inventory was modified server side, the client needs to be
// updated with the change.
assert_packet_count!(sent_packets, 1, S2cPlayPacket::SetContainerSlot(_));
assert_packet_count!(
sent_packets,
1,
S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
Ok(())
}
@ -831,7 +848,7 @@ mod test {
// Make assertions
let sent_packets = client_helper.collect_sent()?;
assert_packet_count!(sent_packets, 1, S2cPlayPacket::SetContainerContent(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_));
Ok(())
}
@ -851,7 +868,7 @@ mod test {
}
#[test]
fn test_should_modify_open_inventory_click_container() -> anyhow::Result<()> {
fn test_should_modify_open_inventory_click_slot() -> anyhow::Result<()> {
let mut app = App::new();
let (client_ent, mut client_helper) = scenario_single_client(&mut app);
let inventory_ent = set_up_open_inventory(&mut app, client_ent);
@ -867,13 +884,16 @@ mod test {
.unwrap()
.inventory_state_id;
let window_id = app.world.get::<Client>(client_ent).unwrap().window_id;
client_helper.send(&valence_protocol::packets::c2s::play::ClickContainer {
client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s {
window_id,
button: 0,
mode: valence_protocol::types::ClickContainerMode::Click,
mode: valence_protocol::packet::c2s::play::click_slot::ClickMode::Click,
state_id: VarInt(state_id.0),
slot_idx: 20,
slots: vec![(20, None)],
slots: vec![valence_protocol::packet::c2s::play::click_slot::Slot {
idx: 20,
item: None,
}],
carried_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)),
});
@ -888,7 +908,7 @@ mod test {
assert_packet_count!(
sent_packets,
0,
S2cPlayPacket::SetContainerContent(_) | S2cPlayPacket::SetContainerSlot(_)
S2cPlayPacket::InventoryS2c(_) | S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
let inventory = app
.world
@ -931,7 +951,11 @@ mod test {
// because the inventory was modified server side, the client needs to be
// updated with the change.
assert_packet_count!(sent_packets, 1, S2cPlayPacket::SetContainerSlot(_));
assert_packet_count!(
sent_packets,
1,
S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
let inventory = app
.world
.get::<Inventory>(inventory_ent)
@ -964,7 +988,7 @@ mod test {
// Make assertions
let sent_packets = client_helper.collect_sent()?;
assert_packet_count!(sent_packets, 1, S2cPlayPacket::SetContainerContent(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_));
Ok(())
}
@ -983,10 +1007,12 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::SetCreativeModeSlot {
slot: 36,
clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)),
});
client_helper.send(
&valence_protocol::packet::c2s::play::CreativeInventoryActionC2s {
slot: 36,
clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)),
},
);
app.update();
@ -1015,10 +1041,12 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::SetCreativeModeSlot {
slot: 36,
clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)),
});
client_helper.send(
&valence_protocol::packet::c2s::play::CreativeInventoryActionC2s {
slot: 36,
clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)),
},
);
app.update();
@ -1075,7 +1103,7 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::SetHeldItemC2s { slot: 4 });
client_helper.send(&valence_protocol::packet::c2s::play::UpdateSelectedSlotC2s { slot: 4 });
app.update();
@ -1090,8 +1118,10 @@ mod test {
}
mod dropping_items {
use valence_protocol::types::{ClickContainerMode, DiggingStatus};
use valence_protocol::{BlockFace, BlockPos};
use valence_protocol::block_pos::BlockPos;
use valence_protocol::packet::c2s::play::click_slot::ClickMode;
use valence_protocol::packet::c2s::play::player_action::Action;
use valence_protocol::types::Direction;
use super::*;
use crate::client::event::DropItemStack;
@ -1110,10 +1140,10 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::PlayerAction {
status: DiggingStatus::DropItem,
client_helper.send(&valence_protocol::packet::c2s::play::PlayerActionC2s {
action: Action::DropItem,
position: BlockPos::new(0, 0, 0),
face: BlockFace::Bottom,
direction: Direction::Down,
sequence: VarInt(0),
});
@ -1142,7 +1172,11 @@ mod test {
);
let sent_packets = client_helper.collect_sent()?;
assert_packet_count!(sent_packets, 0, S2cPlayPacket::SetContainerSlot(_));
assert_packet_count!(
sent_packets,
0,
S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
Ok(())
}
@ -1161,10 +1195,10 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::PlayerAction {
status: DiggingStatus::DropItemStack,
client_helper.send(&valence_protocol::packet::c2s::play::PlayerActionC2s {
action: Action::DropAllItems,
position: BlockPos::new(0, 0, 0),
face: BlockFace::Bottom,
direction: Direction::Down,
sequence: VarInt(0),
});
@ -1206,10 +1240,12 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::SetCreativeModeSlot {
slot: -1,
clicked_item: Some(ItemStack::new(ItemKind::IronIngot, 32, None)),
});
client_helper.send(
&valence_protocol::packet::c2s::play::CreativeInventoryActionC2s {
slot: -1,
clicked_item: Some(ItemStack::new(ItemKind::IronIngot, 32, None)),
},
);
app.update();
@ -1245,11 +1281,11 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::ClickContainer {
client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s {
window_id: 0,
slot_idx: -999,
button: 0,
mode: ClickContainerMode::Click,
mode: ClickMode::Click,
state_id: VarInt(state_id),
slots: vec![],
carried_item: None,
@ -1298,11 +1334,11 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::ClickContainer {
client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s {
window_id: 0,
slot_idx: 40,
button: 0,
mode: ClickContainerMode::DropKey,
mode: ClickMode::DropKey,
state_id: VarInt(state_id),
slots: vec![],
carried_item: None,
@ -1346,11 +1382,11 @@ mod test {
app.update();
client_helper.clear_sent();
client_helper.send(&valence_protocol::packets::c2s::play::ClickContainer {
client_helper.send(&valence_protocol::packet::c2s::play::ClickSlotC2s {
window_id: 0,
slot_idx: 40,
button: 1, // pressing control
mode: ClickContainerMode::DropKey,
mode: ClickMode::DropKey,
state_id: VarInt(state_id),
slots: vec![],
carried_item: None,

View file

@ -66,14 +66,17 @@ pub mod prelude {
pub use player_list::{PlayerList, PlayerListEntry};
pub use protocol::block::{BlockState, PropName, PropValue};
pub use protocol::ident::Ident;
pub use protocol::item::{ItemKind, ItemStack};
pub use protocol::text::{Color, Text, TextFormat};
pub use protocol::types::GameMode;
pub use protocol::username::Username;
pub use protocol::{ident, ItemKind, ItemStack};
pub use server::{EventLoop, NewClientInfo, Server, SharedServer};
pub use uuid::Uuid;
pub use valence_nbt::Compound;
pub use valence_protocol::{BlockKind, BlockPos};
pub use valence_protocol::block::BlockKind;
pub use valence_protocol::block_pos::BlockPos;
pub use valence_protocol::ident;
pub use valence_protocol::packet::s2c::play::particle::Particle;
pub use view::{ChunkPos, ChunkView};
use super::*;

View file

@ -1,7 +1,8 @@
use std::io::Write;
use tracing::warn;
use valence_protocol::{encode_packet, encode_packet_compressed, EncodePacket, PacketEncoder};
use valence_protocol::codec::{encode_packet, encode_packet_compressed, PacketEncoder};
use valence_protocol::EncodePacket;
pub(crate) trait WritePacket {
fn write_packet<P>(&mut self, packet: &P)

View file

@ -7,12 +7,12 @@ use std::mem;
use bevy_ecs::prelude::*;
use tracing::warn;
use uuid::Uuid;
use valence_protocol::packets::s2c::play::{PlayerInfoRemove, SetTabListHeaderAndFooter};
use valence_protocol::packets::s2c::player_info_update::{
Actions, Entry as PlayerInfoEntry, PlayerInfoUpdate,
use valence_protocol::packet::s2c::play::player_list::{
Actions, Entry as PlayerInfoEntry, PlayerListS2c,
};
use valence_protocol::packet::s2c::play::{PlayerListHeaderS2c, PlayerRemoveS2c};
use valence_protocol::text::Text;
use valence_protocol::types::{GameMode, Property};
use valence_protocol::Text;
use crate::client::Client;
use crate::packet::{PacketWriter, WritePacket};
@ -242,14 +242,14 @@ impl PlayerList {
.collect();
if !entries.is_empty() {
writer.write_packet(&PlayerInfoUpdate {
writer.write_packet(&PlayerListS2c {
actions,
entries: entries.into(),
});
}
if !self.header.is_empty() || !self.footer.is_empty() {
writer.write_packet(&SetTabListHeaderAndFooter {
writer.write_packet(&PlayerListHeaderS2c {
header: (&self.header).into(),
footer: (&self.footer).into(),
});
@ -610,7 +610,7 @@ pub(crate) fn update_player_list(
display_name: entry.display_name.as_ref().map(|t| t.into()),
};
writer.write_packet(&PlayerInfoUpdate {
writer.write_packet(&PlayerListS2c {
actions,
entries: Cow::Borrowed(&[packet_entry]),
});
@ -638,7 +638,7 @@ pub(crate) fn update_player_list(
}
if u8::from(actions) != 0 {
writer.write_packet(&PlayerInfoUpdate {
writer.write_packet(&PlayerListS2c {
actions,
entries: Cow::Borrowed(&[PlayerInfoEntry {
player_uuid: uuid,
@ -658,7 +658,7 @@ pub(crate) fn update_player_list(
});
if !removed.is_empty() {
writer.write_packet(&PlayerInfoRemove {
writer.write_packet(&PlayerRemoveS2c {
uuids: removed.into(),
});
}
@ -666,7 +666,7 @@ pub(crate) fn update_player_list(
if pl.modified_header_or_footer {
pl.modified_header_or_footer = false;
writer.write_packet(&SetTabListHeaderAndFooter {
writer.write_packet(&PlayerListHeaderS2c {
header: (&pl.header).into(),
footer: (&pl.footer).into(),
});

View file

@ -17,8 +17,9 @@ use tokio::runtime::{Handle, Runtime};
use tokio::sync::Semaphore;
use uuid::Uuid;
use valence_nbt::{compound, Compound, List};
use valence_protocol::ident;
use valence_protocol::types::Property;
use valence_protocol::{ident, Username};
use valence_protocol::username::Username;
use crate::biome::{validate_biomes, Biome, BiomeId};
use crate::client::event::{event_loop_run_criteria, register_client_events};

View file

@ -21,18 +21,22 @@ use tokio::net::{TcpListener, TcpStream};
use tokio::sync::OwnedSemaphorePermit;
use tracing::{error, info, instrument, trace, warn};
use uuid::Uuid;
use valence_protocol::packets::c2s::handshake::HandshakeOwned;
use valence_protocol::packets::c2s::login::{EncryptionResponse, LoginPluginResponse, LoginStart};
use valence_protocol::packets::c2s::status::{PingRequest, StatusRequest};
use valence_protocol::packets::s2c::login::{
DisconnectLogin, EncryptionRequest, LoginPluginRequest, LoginSuccess, SetCompression,
};
use valence_protocol::packets::s2c::status::{PingResponse, StatusResponse};
use valence_protocol::types::{HandshakeNextState, Property};
use valence_protocol::{
translation_key, Decode, Ident, PacketDecoder, PacketEncoder, RawBytes, Text, Username, VarInt,
MINECRAFT_VERSION, PROTOCOL_VERSION,
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::ident::Ident;
use valence_protocol::packet::c2s::handshake::handshake::NextState;
use valence_protocol::packet::c2s::handshake::HandshakeC2s;
use valence_protocol::packet::c2s::login::{LoginHelloC2s, LoginKeyC2s, LoginQueryResponseC2s};
use valence_protocol::packet::c2s::status::{QueryPingC2s, QueryRequestC2s};
use valence_protocol::packet::s2c::login::{
LoginCompressionS2c, LoginDisconnectS2c, LoginHelloS2c, LoginQueryRequestS2c, LoginSuccessS2c,
};
use valence_protocol::packet::s2c::status::{QueryPongS2c, QueryResponseS2c};
use valence_protocol::raw_bytes::RawBytes;
use valence_protocol::text::Text;
use valence_protocol::types::Property;
use valence_protocol::username::Username;
use valence_protocol::var_int::VarInt;
use valence_protocol::{translation_key, Decode, MINECRAFT_VERSION, PROTOCOL_VERSION};
use crate::config::{AsyncCallbacks, ConnectionMode, ServerListPing};
use crate::server::connection::InitialConnection;
@ -110,13 +114,25 @@ async fn handle_connection(
}
}
struct HandshakeData {
protocol_version: i32,
server_address: String,
next_state: NextState,
}
async fn handle_handshake(
shared: SharedServer,
callbacks: Arc<impl AsyncCallbacks>,
mut conn: InitialConnection<OwnedReadHalf, OwnedWriteHalf>,
remote_addr: SocketAddr,
) -> anyhow::Result<()> {
let handshake = conn.recv_packet::<HandshakeOwned>().await?;
let handshake = conn.recv_packet::<HandshakeC2s>().await?;
let handshake = HandshakeData {
protocol_version: handshake.protocol_version.0,
server_address: handshake.server_address.to_owned(),
next_state: handshake.next_state,
};
ensure!(
matches!(shared.connection_mode(), ConnectionMode::BungeeCord)
@ -125,12 +141,10 @@ async fn handle_handshake(
);
match handshake.next_state {
HandshakeNextState::Status => {
handle_status(shared, callbacks, conn, remote_addr, handshake)
.await
.context("error handling status")
}
HandshakeNextState::Login => {
NextState::Status => handle_status(shared, callbacks, conn, remote_addr, handshake)
.await
.context("error handling status"),
NextState::Login => {
match handle_login(&shared, callbacks, &mut conn, remote_addr, handshake)
.await
.context("error handling login")?
@ -157,12 +171,12 @@ async fn handle_status(
callbacks: Arc<impl AsyncCallbacks>,
mut conn: InitialConnection<OwnedReadHalf, OwnedWriteHalf>,
remote_addr: SocketAddr,
handshake: HandshakeOwned,
handshake: HandshakeData,
) -> anyhow::Result<()> {
conn.recv_packet::<StatusRequest>().await?;
conn.recv_packet::<QueryRequestC2s>().await?;
match callbacks
.server_list_ping(&shared, remote_addr, handshake.protocol_version.0)
.server_list_ping(&shared, remote_addr, handshake.protocol_version)
.await
{
ServerListPing::Respond {
@ -191,7 +205,7 @@ async fn handle_status(
json["favicon"] = Value::String(buf);
}
conn.send_packet(&StatusResponse {
conn.send_packet(&QueryResponseS2c {
json: &json.to_string(),
})
.await?;
@ -199,9 +213,9 @@ async fn handle_status(
ServerListPing::Ignore => return Ok(()),
}
let PingRequest { payload } = conn.recv_packet().await?;
let QueryPingC2s { payload } = conn.recv_packet().await?;
conn.send_packet(&PingResponse { payload }).await?;
conn.send_packet(&QueryPongS2c { payload }).await?;
Ok(())
}
@ -212,14 +226,14 @@ async fn handle_login(
callbacks: Arc<impl AsyncCallbacks>,
conn: &mut InitialConnection<OwnedReadHalf, OwnedWriteHalf>,
remote_addr: SocketAddr,
handshake: HandshakeOwned,
handshake: HandshakeData,
) -> anyhow::Result<Option<NewClientInfo>> {
if handshake.protocol_version.0 != PROTOCOL_VERSION {
if handshake.protocol_version != PROTOCOL_VERSION {
// TODO: send translated disconnect msg?
return Ok(None);
}
let LoginStart {
let LoginHelloC2s {
username,
profile_id: _, // TODO
} = conn.recv_packet().await?;
@ -236,7 +250,7 @@ async fn handle_login(
};
if let Some(threshold) = shared.0.compression_threshold {
conn.send_packet(&SetCompression {
conn.send_packet(&LoginCompressionS2c {
threshold: VarInt(threshold as i32),
})
.await?;
@ -246,14 +260,14 @@ async fn handle_login(
if let Err(reason) = callbacks.login(shared, &info).await {
info!("disconnect at login: \"{reason}\"");
conn.send_packet(&DisconnectLogin {
conn.send_packet(&LoginDisconnectS2c {
reason: reason.into(),
})
.await?;
return Ok(None);
}
conn.send_packet(&LoginSuccess {
conn.send_packet(&LoginSuccessS2c {
uuid: info.uuid,
username: info.username.as_str_username(),
properties: Default::default(),
@ -273,14 +287,14 @@ pub(super) async fn login_online(
) -> anyhow::Result<NewClientInfo> {
let my_verify_token: [u8; 16] = rand::random();
conn.send_packet(&EncryptionRequest {
conn.send_packet(&LoginHelloS2c {
server_id: "", // Always empty
public_key: &shared.0.public_key_der,
verify_token: &my_verify_token,
})
.await?;
let EncryptionResponse {
let LoginKeyC2s {
shared_secret,
verify_token: encrypted_verify_token,
} = conn.recv_packet().await?;
@ -332,7 +346,7 @@ pub(super) async fn login_online(
translation_key::MULTIPLAYER_DISCONNECT_UNVERIFIED_USERNAME,
[],
);
conn.send_packet(&DisconnectLogin {
conn.send_packet(&LoginDisconnectS2c {
reason: reason.into(),
})
.await?;
@ -417,7 +431,7 @@ pub(super) async fn login_velocity(
let message_id: i32 = 0; // TODO: make this random?
// Send Player Info Request into the Plugin Channel
conn.send_packet(&LoginPluginRequest {
conn.send_packet(&LoginQueryRequestS2c {
message_id: VarInt(message_id),
channel: Ident::new("velocity:player_info").unwrap(),
data: RawBytes(&[VELOCITY_MIN_SUPPORTED_VERSION]),
@ -425,7 +439,7 @@ pub(super) async fn login_velocity(
.await?;
// Get Response
let plugin_response: LoginPluginResponse = conn.recv_packet().await?;
let plugin_response: LoginQueryResponseC2s = conn.recv_packet().await?;
ensure!(
plugin_response.message_id.0 == message_id,

View file

@ -9,7 +9,8 @@ use tokio::sync::OwnedSemaphorePermit;
use tokio::task::JoinHandle;
use tokio::time::timeout;
use tracing::debug;
use valence_protocol::{DecodePacket, EncodePacket, PacketDecoder, PacketEncoder};
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::{DecodePacket, EncodePacket};
use crate::client::{Client, ClientConnection};
use crate::server::byte_channel::{

View file

@ -53,7 +53,7 @@ use crate::unit_test::util::scenario_single_client;
/// Some of the tests in this file may be inferior duplicates of real tests.
#[cfg(test)]
mod tests {
use valence_protocol::packets::S2cPlayPacket;
use valence_protocol::packet::S2cPlayPacket;
use super::*;
use crate::client::Client;
@ -80,7 +80,7 @@ mod tests {
let (client_ent, mut client_helper) = scenario_single_client(&mut app);
// Send a packet as the client to the server.
let packet = valence_protocol::packets::c2s::play::SetPlayerPosition {
let packet = valence_protocol::packet::c2s::play::PositionAndOnGroundC2s {
position: [12.0, 64.0, 0.0],
on_ground: true,
};
@ -123,12 +123,12 @@ mod tests {
.expect("client not found");
let sent_packets = client_helper.collect_sent()?;
assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreen(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::SetContainerContent(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreenS2c(_));
assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_));
assert_packet_order!(
sent_packets,
S2cPlayPacket::OpenScreen(_),
S2cPlayPacket::SetContainerContent(_)
S2cPlayPacket::OpenScreenS2c(_),
S2cPlayPacket::InventoryS2c(_)
);
Ok(())

View file

@ -3,8 +3,10 @@ use std::sync::{Arc, Mutex};
use bevy_app::App;
use bevy_ecs::prelude::Entity;
use bytes::BytesMut;
use valence_protocol::packets::S2cPlayPacket;
use valence_protocol::{EncodePacket, PacketDecoder, PacketEncoder, Username};
use valence_protocol::codec::{PacketDecoder, PacketEncoder};
use valence_protocol::packet::S2cPlayPacket;
use valence_protocol::username::Username;
use valence_protocol::EncodePacket;
use crate::client::{Client, ClientConnection};
use crate::config::{ConnectionMode, ServerPlugin};
@ -133,7 +135,7 @@ impl MockClientHelper {
/// Inject a packet to be treated as a packet inbound to the server. Panics
/// if the packet cannot be sent.
pub fn send(&mut self, packet: &impl EncodePacket) {
pub fn send(&mut self, packet: &(impl EncodePacket + ?Sized)) {
self.enc
.append_packet(packet)
.expect("failed to encode packet");
@ -179,7 +181,7 @@ pub fn scenario_single_client(app: &mut App) -> (Entity, MockClientHelper) {
#[macro_export]
macro_rules! assert_packet_order {
($sent_packets:ident, $($packets:pat),+) => {{
let sent_packets: &Vec<valence_protocol::packets::S2cPlayPacket> = &$sent_packets;
let sent_packets: &Vec<valence_protocol::packet::S2cPlayPacket> = &$sent_packets;
let positions = [
$((sent_packets.iter().position(|p| matches!(p, $packets))),)*
];
@ -190,7 +192,7 @@ macro_rules! assert_packet_order {
#[macro_export]
macro_rules! assert_packet_count {
($sent_packets:ident, $count:tt, $packet:pat) => {{
let sent_packets: &Vec<valence_protocol::packets::S2cPlayPacket> = &$sent_packets;
let sent_packets: &Vec<valence_protocol::packet::S2cPlayPacket> = &$sent_packets;
let count = sent_packets.iter().filter(|p| matches!(p, $packet)).count();
assert_eq!(
count,

View file

@ -1,5 +1,5 @@
use glam::DVec3;
use valence_protocol::BlockPos;
use valence_protocol::block_pos::BlockPos;
/// The X and Z position of a chunk in an
/// [`Instance`](crate::instance::Instance).

View file

@ -1,9 +1,9 @@
use num_integer::{div_ceil, Integer};
use thiserror::Error;
use valence::biome::BiomeId;
use valence::instance::Chunk;
use valence::protocol::block::{BlockEntity, BlockEntityKind, BlockKind, PropName, PropValue};
use valence::protocol::Ident;
use valence::instance::{BlockEntity, Chunk};
use valence::protocol::block::{BlockEntityKind, BlockKind, PropName, PropValue};
use valence::protocol::ident::Ident;
use valence_nbt::{Compound, List, Value};
#[derive(Clone, Debug, Error)]

View file

@ -437,10 +437,13 @@ impl<'a> SnbtReader<'a> {
/// Assert that the string has no trailing data.
/// SNBT is quite similar to JSON, but with some differences.
/// See [the wiki](https://minecraft.gamepedia.com/NBT_format#SNBT_format) for more information.
///
/// # Example
///
/// ```
/// use valence_nbt::snbt::SnbtReader;
/// use valence_nbt::snbt::from_snbt_str;
/// use valence_nbt::Value;
///
/// let value = from_snbt_str("1f").unwrap();
/// assert_eq!(value, Value::Float(1.0));
/// ```

View file

@ -1,17 +1,21 @@
use std::borrow::Cow;
use std::time::Duration;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::Rng;
use valence_nbt::{compound, List};
use valence_protocol::array::LengthPrefixedArray;
use valence_protocol::block::{BlockKind, BlockState, PropName, PropValue};
use valence_protocol::packets::s2c::play::{
ChunkDataAndUpdateLight, ChunkDataAndUpdateLightEncode, SetTabListHeaderAndFooter, SpawnEntity,
};
use valence_protocol::text::Color;
use valence_protocol::{
encode_packet, encode_packet_compressed, ByteAngle, Decode, Encode, ItemKind,
LengthPrefixedArray, PacketDecoder, PacketEncoder, TextFormat, VarInt, VarLong,
use valence_protocol::byte_angle::ByteAngle;
use valence_protocol::codec::{
encode_packet, encode_packet_compressed, PacketDecoder, PacketEncoder,
};
use valence_protocol::item::ItemKind;
use valence_protocol::packet::s2c::play::{ChunkDataS2c, EntitySpawnS2c, PlayerListHeaderS2c};
use valence_protocol::text::{Color, TextFormat};
use valence_protocol::var_int::VarInt;
use valence_protocol::var_long::VarLong;
use valence_protocol::{Decode, Encode};
criterion_group! {
name = benches;
@ -112,24 +116,24 @@ fn packets(c: &mut Criterion) {
const SKY_LIGHT_ARRAYS: [LengthPrefixedArray<u8, 2048>; 26] =
[LengthPrefixedArray([0xff; 2048]); 26];
let chunk_data_packet = ChunkDataAndUpdateLightEncode {
let chunk_data_packet = ChunkDataS2c {
chunk_x: 123,
chunk_z: 456,
heightmaps: &compound! {
heightmaps: Cow::Owned(compound! {
"MOTION_BLOCKING" => List::Long(vec![123; 256]),
},
}),
blocks_and_biomes: BLOCKS_AND_BIOMES.as_slice(),
block_entities: &[],
block_entities: Cow::Borrowed(&[]),
trust_edges: false,
sky_light_mask: &[],
block_light_mask: &[],
empty_sky_light_mask: &[],
empty_block_light_mask: &[],
sky_light_arrays: SKY_LIGHT_ARRAYS.as_slice(),
block_light_arrays: &[],
sky_light_mask: Cow::Borrowed(&[]),
block_light_mask: Cow::Borrowed(&[]),
empty_sky_light_mask: Cow::Borrowed(&[]),
empty_block_light_mask: Cow::Borrowed(&[]),
sky_light_arrays: Cow::Borrowed(SKY_LIGHT_ARRAYS.as_slice()),
block_light_arrays: Cow::Borrowed(&[]),
};
let tab_list_header_footer_packet = SetTabListHeaderAndFooter {
let player_list_header_packet = PlayerListHeaderS2c {
header: ("this".italic() + " is the " + "header".bold().color(Color::RED)).into(),
footer: ("this".italic()
+ " is the "
@ -139,7 +143,7 @@ fn packets(c: &mut Criterion) {
.into(),
};
let spawn_entity_packet = SpawnEntity {
let spawn_entity_packet = EntitySpawnS2c {
entity_id: VarInt(1234),
object_uuid: Default::default(),
kind: VarInt(5),
@ -162,14 +166,12 @@ fn packets(c: &mut Criterion) {
});
});
c.bench_function("encode_tab_list_header_footer", |b| {
c.bench_function("encode_player_list_header", |b| {
b.iter(|| {
let encoder = black_box(&mut encoder);
encoder.clear();
encoder
.append_packet(&tab_list_header_footer_packet)
.unwrap();
encoder.append_packet(&player_list_header_packet).unwrap();
black_box(encoder);
});
@ -199,14 +201,12 @@ fn packets(c: &mut Criterion) {
});
});
c.bench_function("encode_tab_list_header_footer_compressed", |b| {
c.bench_function("encode_player_list_header_compressed", |b| {
b.iter(|| {
let encoder = black_box(&mut encoder);
encoder.clear();
encoder
.append_packet(&tab_list_header_footer_packet)
.unwrap();
encoder.append_packet(&player_list_header_packet).unwrap();
black_box(encoder);
});
@ -233,25 +233,21 @@ fn packets(c: &mut Criterion) {
let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf);
decoder
.try_next_packet::<ChunkDataAndUpdateLight>()
.unwrap();
decoder.try_next_packet::<ChunkDataS2c>().unwrap();
black_box(decoder);
});
});
packet_buf.clear();
encode_packet(&mut packet_buf, &tab_list_header_footer_packet).unwrap();
encode_packet(&mut packet_buf, &player_list_header_packet).unwrap();
c.bench_function("decode_tab_list_header_footer", |b| {
c.bench_function("decode_player_list_header", |b| {
b.iter(|| {
let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf);
decoder
.try_next_packet::<SetTabListHeaderAndFooter>()
.unwrap();
decoder.try_next_packet::<PlayerListHeaderS2c>().unwrap();
black_box(decoder);
});
@ -260,12 +256,12 @@ fn packets(c: &mut Criterion) {
packet_buf.clear();
encode_packet(&mut packet_buf, &spawn_entity_packet).unwrap();
c.bench_function("decode_spawn_entity", |b| {
c.bench_function("decode_entity_spawn", |b| {
b.iter(|| {
let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf);
decoder.try_next_packet::<SpawnEntity>().unwrap();
decoder.try_next_packet::<EntitySpawnS2c>().unwrap();
black_box(decoder);
});
@ -283,9 +279,7 @@ fn packets(c: &mut Criterion) {
let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf);
decoder
.try_next_packet::<ChunkDataAndUpdateLight>()
.unwrap();
decoder.try_next_packet::<ChunkDataS2c>().unwrap();
black_box(decoder);
});
@ -294,20 +288,18 @@ fn packets(c: &mut Criterion) {
packet_buf.clear();
encode_packet_compressed(
&mut packet_buf,
&tab_list_header_footer_packet,
&player_list_header_packet,
256,
&mut scratch,
)
.unwrap();
c.bench_function("decode_tab_list_header_footer_compressed", |b| {
c.bench_function("decode_player_list_header_compressed", |b| {
b.iter(|| {
let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf);
decoder
.try_next_packet::<SetTabListHeaderAndFooter>()
.unwrap();
decoder.try_next_packet::<PlayerListHeaderS2c>().unwrap();
black_box(decoder);
});
@ -321,7 +313,7 @@ fn packets(c: &mut Criterion) {
let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf);
decoder.try_next_packet::<SpawnEntity>().unwrap();
decoder.try_next_packet::<EntitySpawnS2c>().unwrap();
black_box(decoder);
});

View file

@ -2,7 +2,8 @@ use std::io::Write;
use anyhow::ensure;
use crate::{Decode, Encode, VarInt};
use crate::var_int::VarInt;
use crate::{Decode, Encode};
/// A fixed-size array encoded and decoded with a [`VarInt`] length prefix.
///

View file

@ -6,10 +6,12 @@ use std::io::Write;
use std::iter::FusedIterator;
use anyhow::Context;
use valence_nbt::Compound;
use valence_protocol_macros::ident_str;
use crate::{Decode, Encode, Ident, ItemKind, Result, VarInt};
use crate::ident::Ident;
use crate::item::ItemKind;
use crate::var_int::VarInt;
use crate::{Decode, Encode, Result};
include!(concat!(env!("OUT_DIR"), "/block.rs"));
@ -81,34 +83,6 @@ impl Decode<'_> for BlockKind {
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BlockEntity {
pub kind: BlockEntityKind,
pub nbt: Compound,
}
impl BlockEntity {
pub const fn new(kind: BlockEntityKind, nbt: Compound) -> Self {
Self { kind, nbt }
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum BlockFace {
/// -Y
Bottom,
/// +Y
Top,
/// -Z
North,
/// +Z
South,
/// -X
West,
/// +X
East,
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -2,7 +2,8 @@ use std::io::Write;
use anyhow::bail;
use crate::{BlockFace, Decode, Encode};
use crate::types::Direction;
use crate::{Decode, Encode};
/// Represents an absolute block position in world space.
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]
@ -27,20 +28,21 @@ impl BlockPos {
/// direction.
///
/// ```
/// use valence_protocol::{BlockFace, BlockPos};
/// use valence_protocol::block_pos::BlockPos;
/// use valence_protocol::types::Direction;
///
/// let pos = BlockPos::new(0, 0, 0);
/// let adj = pos.get_in_direction(BlockFace::South);
/// let adj = pos.get_in_direction(Direction::South);
/// assert_eq!(adj, BlockPos::new(0, 0, 1));
/// ```
pub fn get_in_direction(self, dir: BlockFace) -> BlockPos {
pub fn get_in_direction(self, dir: Direction) -> BlockPos {
match dir {
BlockFace::Bottom => BlockPos::new(self.x, self.y - 1, self.z),
BlockFace::Top => BlockPos::new(self.x, self.y + 1, self.z),
BlockFace::North => BlockPos::new(self.x, self.y, self.z - 1),
BlockFace::South => BlockPos::new(self.x, self.y, self.z + 1),
BlockFace::West => BlockPos::new(self.x - 1, self.y, self.z),
BlockFace::East => BlockPos::new(self.x + 1, self.y, self.z),
Direction::Down => BlockPos::new(self.x, self.y - 1, self.z),
Direction::Up => BlockPos::new(self.x, self.y + 1, self.z),
Direction::North => BlockPos::new(self.x, self.y, self.z - 1),
Direction::South => BlockPos::new(self.x, self.y, self.z + 1),
Direction::West => BlockPos::new(self.x - 1, self.y, self.z),
Direction::East => BlockPos::new(self.x + 1, self.y, self.z),
}
}
}

View file

@ -1,100 +0,0 @@
/*
// TODO: implement BoundedFloat when floats are permitted in const generics.
use std::io::Write;
use anyhow::ensure;
use crate::{Decode, Encode, Result};
/// An integer with a minimum and maximum value known at compile time. `T` is
/// the underlying integer type.
///
/// If the value is not in bounds, an error is generated while
/// encoding or decoding.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct BoundedInt<T, const MIN: i128, const MAX: i128>(pub T);
impl<T, const MIN: i128, const MAX: i128> Encode for BoundedInt<T, MIN, MAX>
where
T: Encode + Clone + Into<i128>,
{
fn encode(&self, w: impl Write) -> Result<()> {
let n = self.0.clone().into();
ensure!(
(MIN..=MAX).contains(&n),
"integer is not in bounds while encoding (got {n}, expected {MIN}..={MAX})"
);
self.0.encode(w)
}
}
impl<'a, T, const MIN: i128, const MAX: i128> Decode<'a> for BoundedInt<T, MIN, MAX>
where
T: Decode<'a> + Clone + Into<i128>,
{
fn decode(r: &mut &'a [u8]) -> Result<Self> {
let res = T::decode(r)?;
let n = res.clone().into();
ensure!(
(MIN..=MAX).contains(&n),
"integer is not in bounds while decoding (got {n}, expected {MIN}..={MAX})"
);
Ok(Self(res))
}
}
/// A string with a minimum and maximum character length known at compile time.
/// `S` is the underlying string type which is anything that implements
/// `AsRef<str>`.
///
/// If the string is not in bounds, an error is generated while
/// encoding or decoding.
///
/// Note that the length is a count of the _characters_ in the string, not
/// bytes.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct BoundedString<S, const MIN: usize, const MAX: usize>(pub S);
impl<S, const MIN: usize, const MAX: usize> Encode for BoundedString<S, MIN, MAX>
where
S: AsRef<str>,
{
fn encode(&self, w: impl Write) -> Result<()> {
let s = self.0.as_ref();
let cnt = s.chars().count();
ensure!(
(MIN..=MAX).contains(&s.chars().count()),
"char count of string is out of bounds while encoding (got {cnt}, expected \
{MIN}..={MAX})"
);
s.encode(w)?;
Ok(())
}
}
impl<'a, S, const MIN: usize, const MAX: usize> Decode<'a> for BoundedString<S, MIN, MAX>
where
S: Decode<'a> + AsRef<str>,
{
fn decode(r: &mut &'a [u8]) -> Result<Self> {
let s = S::decode(r)?;
let cnt = s.as_ref().chars().count();
ensure!(
(MIN..=MAX).contains(&cnt),
"char count of string is out of bounds while decoding (got {cnt}, expected \
{MIN}..={MAX})"
);
Ok(Self(s))
}
}
*/

View file

@ -501,10 +501,10 @@ impl PacketDecoder {
mod tests {
use super::*;
use crate::block_pos::BlockPos;
use crate::entity_meta::PaintingKind;
use crate::ident::Ident;
use crate::item::{ItemKind, ItemStack};
use crate::text::{Text, TextFormat};
use crate::tracked_data::PaintingKind;
use crate::username::Username;
use crate::var_long::VarLong;
use crate::Decode;

View file

@ -12,7 +12,8 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use uuid::Uuid;
use valence_nbt::Compound;
use crate::{Decode, Encode, Result, VarInt, MAX_PACKET_SIZE};
use crate::var_int::VarInt;
use crate::{Decode, Encode, Result, MAX_PACKET_SIZE};
// ==== Primitive ==== //
@ -22,6 +23,7 @@ impl Encode for bool {
}
fn write_slice(slice: &[bool], mut w: impl Write) -> io::Result<()> {
// SAFETY: Bools have the same layout as u8.
// Bools are guaranteed to have the correct bit pattern.
let bytes: &[u8] = unsafe { mem::transmute(slice) };
w.write_all(bytes)

View file

@ -3,7 +3,9 @@ use std::io::Write;
use anyhow::{ensure, Context};
use valence_nbt::Compound;
use crate::{BlockKind, Decode, Encode, Result, VarInt};
use crate::block::BlockKind;
use crate::var_int::VarInt;
use crate::{Decode, Encode, Result};
include!(concat!(env!("OUT_DIR"), "/item.rs"));

View file

@ -2,18 +2,21 @@
//! protocol.
//!
//! The API is centered around the [`Encode`] and [`Decode`] traits. Clientbound
//! and serverbound packets are defined in the [`packets`] module. Packets are
//! and serverbound packets are defined in the [`packet`] module. Packets are
//! encoded and decoded using the [`PacketEncoder`] and [`PacketDecoder`] types.
//!
//! [`PacketEncoder`]: codec::PacketEncoder
//! [`PacketDecoder`]: codec::PacketDecoder
//!
//! # Examples
//!
//! ```
//! use valence_protocol::packets::c2s::play::RenameItem;
//! use valence_protocol::{PacketDecoder, PacketEncoder};
//! use valence_protocol::codec::{PacketDecoder, PacketEncoder};
//! use valence_protocol::packet::c2s::play::RenameItemC2s;
//!
//! let mut enc = PacketEncoder::new();
//!
//! let outgoing = RenameItem {
//! let outgoing = RenameItemC2s {
//! item_name: "Hello!",
//! };
//!
@ -23,7 +26,7 @@
//!
//! dec.queue_bytes(enc.take());
//!
//! let incoming = dec.try_next_packet::<RenameItem>().unwrap().unwrap();
//! let incoming = dec.try_next_packet::<RenameItemC2s>().unwrap().unwrap();
//!
//! assert_eq!(outgoing.item_name, incoming.item_name);
//! ```
@ -72,21 +75,7 @@ use std::io::Write;
use std::{fmt, io};
pub use anyhow::{Error, Result};
pub use array::LengthPrefixedArray;
pub use block::{BlockFace, BlockKind, BlockState};
pub use block_pos::BlockPos;
pub use byte_angle::ByteAngle;
pub use codec::*;
pub use ident::Ident;
pub use item::{ItemKind, ItemStack};
pub use raw_bytes::RawBytes;
pub use sound::Sound;
pub use text::{Text, TextFormat};
pub use username::Username;
pub use uuid::Uuid;
pub use valence_protocol_macros::{Decode, DecodePacket, Encode, EncodePacket};
pub use var_int::VarInt;
pub use var_long::VarLong;
pub use valence_protocol_macros::{ident_str, Decode, DecodePacket, Encode, EncodePacket};
pub use {uuid, valence_nbt as nbt};
/// The Minecraft protocol version this library currently targets.
@ -96,33 +85,33 @@ pub const PROTOCOL_VERSION: i32 = 761;
/// targets.
pub const MINECRAFT_VERSION: &str = "1.19.3";
mod array;
pub mod array;
pub mod block;
mod block_pos;
mod bounded;
mod byte_angle;
mod codec;
pub mod block_pos;
pub mod byte_angle;
pub mod codec;
pub mod enchant;
pub mod entity_meta;
pub mod ident;
mod impls;
mod item;
pub mod packets;
mod raw_bytes;
pub mod item;
pub mod packet;
pub mod raw_bytes;
pub mod sound;
pub mod text;
pub mod tracked_data;
pub mod translation_key;
pub mod types;
pub mod username;
pub mod var_int;
mod var_long;
pub mod var_long;
/// Used only by proc macros. Not public API.
#[doc(hidden)]
pub mod __private {
pub use anyhow::{anyhow, bail, ensure, Context, Result};
pub use crate::{Decode, DecodePacket, Encode, EncodePacket, VarInt};
pub use crate::var_int::VarInt;
pub use crate::{Decode, DecodePacket, Encode, EncodePacket};
}
/// The maximum number of bytes in a single Minecraft packet.
@ -175,6 +164,7 @@ pub const MAX_PACKET_SIZE: i32 = 2097152;
/// ```
///
/// [macro]: valence_protocol_macros::Encode
/// [`VarInt`]: var_int::VarInt
pub trait Encode {
/// Writes this object to the provided writer.
///
@ -250,6 +240,7 @@ pub trait Encode {
/// ```
///
/// [macro]: valence_protocol_macros::Decode
/// [`VarInt`]: var_int::VarInt
pub trait Decode<'a>: Sized {
/// Reads this object from the provided byte slice.
///
@ -285,6 +276,7 @@ pub trait Decode<'a>: Sized {
/// ```
///
/// [macro]: valence_protocol_macros::DecodePacket
/// [`VarInt`]: var_int::VarInt
pub trait EncodePacket: fmt::Debug {
/// The packet ID that is written when [`Self::encode_packet`] is called. A
/// negative value indicates that the packet ID is not statically known.
@ -292,6 +284,8 @@ pub trait EncodePacket: fmt::Debug {
/// Like [`Encode::encode`], but a leading [`VarInt`] packet ID must be
/// written first.
///
/// [`VarInt`]: var_int::VarInt
fn encode_packet(&self, w: impl Write) -> Result<()>;
}
@ -324,6 +318,7 @@ pub trait EncodePacket: fmt::Debug {
/// ```
///
/// [macro]: valence_protocol::DecodePacket
/// [`VarInt`]: var_int::VarInt
pub trait DecodePacket<'a>: Sized + fmt::Debug {
/// The packet ID that is read when [`Self::decode_packet`] is called. A
/// negative value indicates that the packet ID is not statically known.
@ -331,6 +326,8 @@ pub trait DecodePacket<'a>: Sized + fmt::Debug {
/// Like [`Decode::decode`], but a leading [`VarInt`] packet ID must be read
/// first.
///
/// [`VarInt`]: var_int::VarInt
fn decode_packet(r: &mut &'a [u8]) -> Result<Self>;
}
@ -361,14 +358,14 @@ mod derive_tests {
#[derive(Encode, EncodePacket, Decode, DecodePacket, Debug)]
#[packet_id = 5]
struct StructWithGenerics<'z, T: std::fmt::Debug = ()> {
struct StructWithGenerics<'z, T: fmt::Debug = ()> {
foo: &'z str,
bar: T,
}
#[derive(Encode, EncodePacket, Decode, DecodePacket, Debug)]
#[packet_id = 6]
struct TupleStructWithGenerics<'z, T: std::fmt::Debug = ()>(&'z str, i32, T);
struct TupleStructWithGenerics<'z, T: fmt::Debug = ()>(&'z str, i32, T);
#[derive(Encode, EncodePacket, Decode, DecodePacket, Debug)]
#[packet_id = 7]
@ -384,7 +381,7 @@ mod derive_tests {
#[derive(Encode, EncodePacket, Decode, DecodePacket, Debug)]
#[packet_id = 0xbeef]
enum EnumWithGenericsAndTags<'z, T: std::fmt::Debug = ()> {
enum EnumWithGenericsAndTags<'z, T: fmt::Debug = ()> {
#[tag = 5]
First {
foo: &'z str,

View file

@ -12,12 +12,13 @@ pub use s2c::login::S2cLoginPacket;
pub use s2c::play::S2cPlayPacket;
pub use s2c::status::S2cStatusPacket;
/// Defines an enum of packets.
macro_rules! packet_enum {
/// Defines an enum of packets and implements `EncodePacket` and `DecodePacket`
/// for each.
macro_rules! packet_group {
(
$(#[$attrs:meta])*
$enum_name:ident<$enum_life:lifetime> {
$($packet:ident $(<$life:lifetime>)?),* $(,)?
$($packet_id:literal = $packet:ident $(<$life:lifetime>)?),* $(,)?
}
) => {
$(#[$attrs])*
@ -33,11 +34,41 @@ macro_rules! packet_enum {
Self::$packet(p)
}
}
impl$(<$life>)? crate::EncodePacket for $packet$(<$life>)? {
const PACKET_ID: i32 = $packet_id;
#[allow(unused_imports)]
fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> {
use ::valence_protocol::__private::{Encode, Context, VarInt};
VarInt($packet_id)
.encode(&mut w)
.context("failed to encode packet ID")?;
self.encode(w)
}
}
impl<$enum_life> crate::DecodePacket<$enum_life> for $packet$(<$life>)? {
const PACKET_ID: i32 = $packet_id;
#[allow(unused_imports)]
fn decode_packet(r: &mut &$enum_life [u8]) -> ::valence_protocol::__private::Result<Self> {
use ::valence_protocol::__private::{Decode, Context, VarInt, ensure};
let id = VarInt::decode(r).context("failed to decode packet ID")?.0;
ensure!(id == $packet_id, "unexpected packet ID {} (expected {})", id, $packet_id);
Self::decode(r)
}
}
)*
impl<$enum_life> crate::EncodePacket for $enum_name<$enum_life> {
fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> {
use crate::{Encode, VarInt};
use crate::Encode;
use crate::var_int::VarInt;
match self {
$(
@ -54,7 +85,8 @@ macro_rules! packet_enum {
impl<$enum_life> crate::DecodePacket<$enum_life> for $enum_name<$enum_life> {
fn decode_packet(r: &mut &$enum_life [u8]) -> crate::Result<Self> {
use crate::{Decode, VarInt};
use crate::Decode;
use crate::var_int::VarInt;
let id = VarInt::decode(r)?.0;
Ok(match id {
@ -62,7 +94,7 @@ macro_rules! packet_enum {
<$packet as crate::DecodePacket>::PACKET_ID =>
Self::$packet($packet::decode(r)?),
)*
id => anyhow::bail!("unknown packet ID {:#02x} while decoding {}", id, stringify!($enum_name)),
id => anyhow::bail!("unknown packet ID {} while decoding {}", id, stringify!($enum_name)),
})
}
}
@ -81,7 +113,7 @@ macro_rules! packet_enum {
(
$(#[$attrs:meta])*
$enum_name:ident {
$($packet:ident),* $(,)?
$($packet_id:literal = $packet:ident),* $(,)?
}
) => {
$(#[$attrs])*
@ -97,11 +129,41 @@ macro_rules! packet_enum {
Self::$packet(p)
}
}
impl crate::EncodePacket for $packet {
const PACKET_ID: i32 = $packet_id;
#[allow(unused_imports)]
fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> {
use ::valence_protocol::__private::{Encode, Context, VarInt};
VarInt($packet_id)
.encode(&mut w)
.context("failed to encode packet ID")?;
self.encode(w)
}
}
impl crate::DecodePacket<'_> for $packet {
const PACKET_ID: i32 = $packet_id;
#[allow(unused_imports)]
fn decode_packet(r: &mut &[u8]) -> ::valence_protocol::__private::Result<Self> {
use ::valence_protocol::__private::{Decode, Context, VarInt, ensure};
let id = VarInt::decode(r).context("failed to decode packet ID")?.0;
ensure!(id == $packet_id, "unexpected packet ID {} (expected {})", id, $packet_id);
Self::decode(r)
}
}
)*
impl crate::EncodePacket for $enum_name {
fn encode_packet(&self, mut w: impl std::io::Write) -> crate::Result<()> {
use crate::{Encode, VarInt};
use crate::Encode;
use crate::var_int::VarInt;
match self {
$(
@ -118,7 +180,8 @@ macro_rules! packet_enum {
impl crate::DecodePacket<'_> for $enum_name {
fn decode_packet(r: &mut &[u8]) -> crate::Result<Self> {
use crate::{Decode, VarInt};
use crate::Decode;
use crate::var_int::VarInt;
let id = VarInt::decode(r)?.0;
Ok(match id {
@ -126,7 +189,7 @@ macro_rules! packet_enum {
<$packet as crate::DecodePacket>::PACKET_ID =>
Self::$packet($packet::decode(r)?),
)*
id => anyhow::bail!("unknown packet ID {:#02x} while decoding {}", id, stringify!($enum_name)),
id => anyhow::bail!("unknown packet ID {} while decoding {}", id, stringify!($enum_name)),
})
}
}

View file

@ -0,0 +1,205 @@
pub mod handshake {
pub use handshake::HandshakeC2s;
#[allow(clippy::module_inception)]
pub mod handshake;
packet_group! {
#[derive(Clone)]
C2sHandshakePacket<'a> {
0 = HandshakeC2s<'a>
}
}
}
pub mod status {
pub use query_ping::QueryPingC2s;
pub use query_request::QueryRequestC2s;
pub mod query_ping;
pub mod query_request;
packet_group! {
#[derive(Clone)]
C2sStatusPacket {
0 = QueryRequestC2s,
1 = QueryPingC2s,
}
}
}
pub mod login {
pub use login_hello::LoginHelloC2s;
pub use login_key::LoginKeyC2s;
pub use login_query_response::LoginQueryResponseC2s;
pub mod login_hello;
pub mod login_key;
pub mod login_query_response;
packet_group! {
#[derive(Clone)]
C2sLoginPacket<'a> {
0 = LoginHelloC2s<'a>,
1 = LoginKeyC2s<'a>,
2 = LoginQueryResponseC2s<'a>,
}
}
}
pub mod play {
pub use advancement_tab::AdvancementTabC2s;
pub use boat_paddle::BoatPaddleStateC2s;
pub use book_update::BookUpdateC2s;
pub use button_click::ButtonClickC2s;
pub use chat_message::ChatMessageC2s;
pub use click_slot::ClickSlotC2s;
pub use client_command::ClientCommandC2s;
pub use client_settings::ClientSettingsC2s;
pub use client_status::ClientStatusC2s;
pub use close_handled_screen::CloseHandledScreenC2s;
pub use command_execution::CommandExecutionC2s;
pub use craft_request::CraftRequestC2s;
pub use creative_inventory_action::CreativeInventoryActionC2s;
pub use custom_payload::CustomPayloadC2s;
pub use hand_swing::HandSwingC2s;
pub use jigsaw_generating::JigsawGeneratingC2s;
pub use keep_alive::KeepAliveC2s;
pub use message_acknowledgment::MessageAcknowledgmentC2s;
pub use pick_from_inventory::PickFromInventoryC2s;
pub use play_pong::PlayPongC2s;
pub use player_action::PlayerActionC2s;
pub use player_input::PlayerInputC2s;
pub use player_interact::PlayerInteractC2s;
pub use player_interact_block::PlayerInteractBlockC2s;
pub use player_interact_item::PlayerInteractItemC2s;
pub use player_move::{FullC2s, LookAndOnGroundC2s, OnGroundOnlyC2s, PositionAndOnGroundC2s};
pub use player_session::PlayerSessionC2s;
pub use query_block_nbt::QueryBlockNbtC2s;
pub use query_entity_nbt::QueryEntityNbtC2s;
pub use recipe_book_data::RecipeBookDataC2s;
pub use recipe_category_options::RecipeCategoryOptionsC2s;
pub use rename_item::RenameItemC2s;
pub use request_command_completions::RequestCommandCompletionsC2s;
pub use resource_pack_status::ResourcePackStatusC2s;
pub use select_merchant_trade::SelectMerchantTradeC2s;
pub use spectator_teleport::SpectatorTeleportC2s;
pub use teleport_confirm::TeleportConfirmC2s;
pub use update_beacon::UpdateBeaconC2s;
pub use update_command_block::UpdateCommandBlockC2s;
pub use update_command_block_minecart::UpdateCommandBlockMinecartC2s;
pub use update_difficulty::UpdateDifficultyC2s;
pub use update_difficulty_lock::UpdateDifficultyLockC2s;
pub use update_jigsaw::UpdateJigsawC2s;
pub use update_player_abilities::UpdatePlayerAbilitiesC2s;
pub use update_selected_slot::UpdateSelectedSlotC2s;
pub use update_sign::UpdateSignC2s;
pub use update_structure_block::UpdateStructureBlockC2s;
pub use vehicle_move::VehicleMoveC2s;
pub mod advancement_tab;
pub mod boat_paddle;
pub mod book_update;
pub mod button_click;
pub mod chat_message;
pub mod click_slot;
pub mod client_command;
pub mod client_settings;
pub mod client_status;
pub mod close_handled_screen;
pub mod command_execution;
pub mod craft_request;
pub mod creative_inventory_action;
pub mod custom_payload;
pub mod hand_swing;
pub mod jigsaw_generating;
pub mod keep_alive;
pub mod message_acknowledgment;
pub mod pick_from_inventory;
pub mod play_pong;
pub mod player_action;
pub mod player_input;
pub mod player_interact;
pub mod player_interact_block;
pub mod player_interact_item;
pub mod player_move;
pub mod player_session;
pub mod query_block_nbt;
pub mod query_entity_nbt;
pub mod recipe_book_data;
pub mod recipe_category_options;
pub mod rename_item;
pub mod request_command_completions;
pub mod resource_pack_status;
pub mod select_merchant_trade;
pub mod spectator_teleport;
pub mod teleport_confirm;
pub mod update_beacon;
pub mod update_command_block;
pub mod update_command_block_minecart;
pub mod update_difficulty;
pub mod update_difficulty_lock;
pub mod update_jigsaw;
pub mod update_player_abilities;
pub mod update_selected_slot;
pub mod update_sign;
pub mod update_structure_block;
pub mod vehicle_move;
packet_group! {
#[derive(Clone)]
C2sPlayPacket<'a> {
0 = TeleportConfirmC2s,
1 = QueryBlockNbtC2s,
2 = UpdateDifficultyC2s,
3 = MessageAcknowledgmentC2s,
4 = CommandExecutionC2s<'a>,
5 = ChatMessageC2s<'a>,
6 = ClientStatusC2s,
7 = ClientSettingsC2s<'a>,
8 = RequestCommandCompletionsC2s<'a>,
9 = ButtonClickC2s,
10 = ClickSlotC2s,
11 = CloseHandledScreenC2s,
12 = CustomPayloadC2s<'a>,
13 = BookUpdateC2s<'a>,
14 = QueryEntityNbtC2s,
15 = PlayerInteractC2s,
16 = JigsawGeneratingC2s,
17 = KeepAliveC2s,
18 = UpdateDifficultyLockC2s,
19 = PositionAndOnGroundC2s,
20 = FullC2s,
21 = LookAndOnGroundC2s,
22 = OnGroundOnlyC2s,
23 = VehicleMoveC2s,
24 = BoatPaddleStateC2s,
25 = PickFromInventoryC2s,
26 = CraftRequestC2s<'a>,
27 = UpdatePlayerAbilitiesC2s,
28 = PlayerActionC2s,
29 = ClientCommandC2s,
30 = PlayerInputC2s,
31 = PlayPongC2s,
32 = PlayerSessionC2s<'a>,
33 = RecipeCategoryOptionsC2s,
34 = RecipeBookDataC2s<'a>,
35 = RenameItemC2s<'a>,
36 = ResourcePackStatusC2s,
37 = AdvancementTabC2s<'a>,
38 = SelectMerchantTradeC2s,
39 = UpdateBeaconC2s,
40 = UpdateSelectedSlotC2s,
41 = UpdateCommandBlockC2s<'a>,
42 = UpdateCommandBlockMinecartC2s<'a>,
43 = CreativeInventoryActionC2s,
44 = UpdateJigsawC2s<'a>,
45 = UpdateStructureBlockC2s<'a>,
46 = UpdateSignC2s<'a>,
47 = HandSwingC2s,
48 = SpectatorTeleportC2s,
49 = PlayerInteractBlockC2s,
50 = PlayerInteractItemC2s
}
}
}

View file

@ -0,0 +1,18 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct HandshakeC2s<'a> {
pub protocol_version: VarInt,
pub server_address: &'a str,
pub server_port: u16,
pub next_state: NextState,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Encode, Decode)]
pub enum NextState {
#[tag = 1]
Status,
#[tag = 2]
Login,
}

View file

@ -0,0 +1,10 @@
use uuid::Uuid;
use crate::username::Username;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct LoginHelloC2s<'a> {
pub username: Username<&'a str>,
pub profile_id: Option<Uuid>,
}

View file

@ -0,0 +1,7 @@
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct LoginKeyC2s<'a> {
pub shared_secret: &'a [u8],
pub verify_token: &'a [u8],
}

View file

@ -0,0 +1,9 @@
use crate::raw_bytes::RawBytes;
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct LoginQueryResponseC2s<'a> {
pub message_id: VarInt,
pub data: Option<RawBytes<'a>>,
}

View file

@ -0,0 +1,8 @@
use crate::ident::Ident;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub enum AdvancementTabC2s<'a> {
OpenedTab { tab_id: Ident<&'a str> },
ClosedScreen,
}

View file

@ -0,0 +1,7 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct BoatPaddleStateC2s {
pub left_paddle_turning: bool,
pub right_paddle_turning: bool,
}

View file

@ -0,0 +1,9 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct BookUpdateC2s<'a> {
pub slot: VarInt,
pub entries: Vec<&'a str>,
pub title: Option<&'a str>,
}

View file

@ -0,0 +1,7 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct ButtonClickC2s {
pub window_id: i8,
pub button_id: i8,
}

View file

@ -0,0 +1,15 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct ChatMessageC2s<'a> {
pub message: &'a str,
pub timestamp: u64,
pub salt: u64,
pub signature: Option<&'a [u8; 256]>,
pub message_count: VarInt,
// This is a bitset of 20; each bit represents one
// of the last 20 messages received and whether or not
// the message was acknowledged by the client
pub acknowledgement: [u8; 3],
}

View file

@ -0,0 +1,31 @@
use crate::item::ItemStack;
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct ClickSlotC2s {
pub window_id: u8,
pub state_id: VarInt,
pub slot_idx: i16,
pub button: i8,
pub mode: ClickMode,
pub slots: Vec<Slot>,
pub carried_item: Option<ItemStack>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Encode, Decode)]
pub enum ClickMode {
Click,
ShiftClick,
Hotbar,
CreativeMiddleClick,
DropKey,
Drag,
DoubleClick,
}
#[derive(Clone, Debug, Encode, Decode)]
pub struct Slot {
pub idx: i16,
pub item: Option<ItemStack>,
}

View file

@ -0,0 +1,22 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct ClientCommandC2s {
pub entity_id: VarInt,
pub action: Action,
pub jump_boost: VarInt,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum Action {
StartSneaking,
StopSneaking,
LeaveBed,
StartSprinting,
StopSprinting,
StartJumpWithHorse,
StopJumpWithHorse,
OpenHorseInventory,
StartFlyingWithElytra,
}

View file

@ -0,0 +1,42 @@
use bitfield_struct::bitfield;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct ClientSettingsC2s<'a> {
pub locale: &'a str,
pub view_distance: u8,
pub chat_mode: ChatMode,
pub chat_colors: bool,
pub displayed_skin_parts: DisplayedSkinParts,
pub main_hand: MainHand,
pub enable_text_filtering: bool,
pub allow_server_listings: bool,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum ChatMode {
Enabled,
CommandsOnly,
Hidden,
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
pub struct DisplayedSkinParts {
pub cape: bool,
pub jacket: bool,
pub left_sleeve: bool,
pub right_sleeve: bool,
pub left_pants_leg: bool,
pub right_pants_leg: bool,
pub hat: bool,
_pad: bool,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode)]
pub enum MainHand {
Left,
#[default]
Right,
}

View file

@ -0,0 +1,7 @@
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub enum ClientStatusC2s {
PerformRespawn,
RequestStats,
}

View file

@ -0,0 +1,6 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct CloseHandledScreenC2s {
pub window_id: i8,
}

View file

@ -0,0 +1,21 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct CommandExecutionC2s<'a> {
pub command: &'a str,
pub timestamp: u64,
pub salt: u64,
pub argument_signatures: Vec<CommandArgumentSignature<'a>>,
pub message_count: VarInt,
//// This is a bitset of 20; each bit represents one
//// of the last 20 messages received and whether or not
//// the message was acknowledged by the client
pub acknowledgement: [u8; 3],
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct CommandArgumentSignature<'a> {
pub argument_name: &'a str,
pub signature: &'a [u8; 256],
}

View file

@ -0,0 +1,9 @@
use crate::ident::Ident;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct CraftRequestC2s<'a> {
pub window_id: i8,
pub recipe: Ident<&'a str>,
pub make_all: bool,
}

View file

@ -0,0 +1,8 @@
use crate::item::ItemStack;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct CreativeInventoryActionC2s {
pub slot: i16,
pub clicked_item: Option<ItemStack>,
}

View file

@ -0,0 +1,9 @@
use crate::ident::Ident;
use crate::raw_bytes::RawBytes;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct CustomPayloadC2s<'a> {
pub channel: Ident<&'a str>,
pub data: RawBytes<'a>,
}

View file

@ -0,0 +1,7 @@
use crate::types::Hand;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct HandSwingC2s {
pub hand: Hand,
}

View file

@ -0,0 +1,10 @@
use crate::block_pos::BlockPos;
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct JigsawGeneratingC2s {
pub position: BlockPos,
pub levels: VarInt,
pub keep_jigsaws: bool,
}

View file

@ -0,0 +1,6 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct KeepAliveC2s {
pub id: u64,
}

View file

@ -0,0 +1,7 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct MessageAcknowledgmentC2s {
pub message_count: VarInt,
}

View file

@ -0,0 +1,7 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PickFromInventoryC2s {
pub slot_to_use: VarInt,
}

View file

@ -0,0 +1,6 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayPongC2s {
pub id: i32,
}

View file

@ -0,0 +1,23 @@
use crate::block_pos::BlockPos;
use crate::types::Direction;
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayerActionC2s {
pub action: Action,
pub position: BlockPos,
pub direction: Direction,
pub sequence: VarInt,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum Action {
StartDestroyBlock,
AbortDestroyBlock,
StopDestroyBlock,
DropAllItems,
DropItem,
ReleaseUseItem,
SwapItemWithOffhand,
}

View file

@ -0,0 +1,19 @@
use bitfield_struct::bitfield;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayerInputC2s {
pub sideways: f32,
pub forward: f32,
pub flags: Flags,
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
pub struct Flags {
pub jump: bool,
pub unmount: bool,
#[bits(6)]
_pad: u8,
}

View file

@ -0,0 +1,17 @@
use crate::types::Hand;
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayerInteractC2s {
pub entity_id: VarInt,
pub interact: Interaction,
pub sneaking: bool,
}
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)]
pub enum Interaction {
Interact(Hand),
Attack,
InteractAt { target: [f32; 3], hand: Hand },
}

View file

@ -0,0 +1,14 @@
use crate::block_pos::BlockPos;
use crate::types::{Direction, Hand};
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayerInteractBlockC2s {
pub hand: Hand,
pub position: BlockPos,
pub face: Direction,
pub cursor_pos: [f32; 3],
pub head_inside_block: bool,
pub sequence: VarInt,
}

View file

@ -0,0 +1,9 @@
use crate::types::Hand;
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayerInteractItemC2s {
pub hand: Hand,
pub sequence: VarInt,
}

View file

@ -0,0 +1,27 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PositionAndOnGroundC2s {
pub position: [f64; 3],
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct FullC2s {
pub position: [f64; 3],
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct LookAndOnGroundC2s {
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct OnGroundOnlyC2s {
pub on_ground: bool,
}

View file

@ -0,0 +1,12 @@
use uuid::Uuid;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayerSessionC2s<'a> {
pub session_id: Uuid,
// Public key
pub expires_at: i64,
pub public_key_data: &'a [u8],
pub key_signature: &'a [u8],
}

View file

@ -0,0 +1,9 @@
use crate::block_pos::BlockPos;
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct QueryBlockNbtC2s {
pub transaction_id: VarInt,
pub position: BlockPos,
}

View file

@ -0,0 +1,8 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct QueryEntityNbtC2s {
pub transaction_id: VarInt,
pub entity_id: VarInt,
}

View file

@ -0,0 +1,7 @@
use crate::ident::Ident;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct RecipeBookDataC2s<'a> {
pub recipe_id: Ident<&'a str>,
}

View file

@ -0,0 +1,16 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct RecipeCategoryOptionsC2s {
pub book_id: RecipeBookId,
pub book_open: bool,
pub filter_active: bool,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum RecipeBookId {
Crafting,
Furnace,
BlastFurnace,
Smoker,
}

View file

@ -0,0 +1,6 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct RenameItemC2s<'a> {
pub item_name: &'a str,
}

View file

@ -0,0 +1,8 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct RequestCommandCompletionsC2s<'a> {
pub transaction_id: VarInt,
pub text: &'a str,
}

View file

@ -0,0 +1,9 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub enum ResourcePackStatusC2s {
SuccessfullyLoaded,
Declined,
FailedDownload,
Accepted,
}

View file

@ -0,0 +1,7 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct SelectMerchantTradeC2s {
pub selected_slot: VarInt,
}

View file

@ -0,0 +1,8 @@
use uuid::Uuid;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct SpectatorTeleportC2s {
pub target: Uuid,
}

View file

@ -0,0 +1,7 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct TeleportConfirmC2s {
pub teleport_id: VarInt,
}

View file

@ -0,0 +1,9 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateBeaconC2s {
// TODO: extract effect IDs?
pub primary_effect: Option<VarInt>,
pub secondary_effect: Option<VarInt>,
}

View file

@ -0,0 +1,29 @@
use bitfield_struct::bitfield;
use crate::block_pos::BlockPos;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateCommandBlockC2s<'a> {
pub position: BlockPos,
pub command: &'a str,
pub mode: Mode,
pub flags: Flags,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum Mode {
Sequence,
Auto,
Redstone,
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
pub struct Flags {
pub track_output: bool,
pub conditional: bool,
pub automatic: bool,
#[bits(5)]
_pad: u8,
}

View file

@ -0,0 +1,9 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateCommandBlockMinecartC2s<'a> {
pub entity_id: VarInt,
pub command: &'a str,
pub track_output: bool,
}

View file

@ -0,0 +1,7 @@
use crate::types::Difficulty;
use crate::{Decode, Encode};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub struct UpdateDifficultyC2s {
pub difficulty: Difficulty,
}

View file

@ -0,0 +1,6 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateDifficultyLockC2s {
pub locked: bool,
}

View file

@ -0,0 +1,13 @@
use crate::block_pos::BlockPos;
use crate::ident::Ident;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateJigsawC2s<'a> {
pub position: BlockPos,
pub name: Ident<&'a str>,
pub target: Ident<&'a str>,
pub pool: Ident<&'a str>,
pub final_state: &'a str,
pub joint_type: &'a str,
}

View file

@ -0,0 +1,9 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub enum UpdatePlayerAbilitiesC2s {
#[tag = 0b00]
StopFlying,
#[tag = 0b10]
StartFlying,
}

View file

@ -0,0 +1,6 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateSelectedSlotC2s {
pub slot: i16,
}

View file

@ -0,0 +1,8 @@
use crate::block_pos::BlockPos;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateSignC2s<'a> {
pub position: BlockPos,
pub lines: [&'a str; 4],
}

View file

@ -0,0 +1,62 @@
use bitfield_struct::bitfield;
use crate::block_pos::BlockPos;
use crate::var_long::VarLong;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateStructureBlockC2s<'a> {
pub position: BlockPos,
pub action: Action,
pub mode: Mode,
pub name: &'a str,
pub offset_xyz: [i8; 3],
pub size_xyz: [i8; 3],
pub mirror: Mirror,
pub rotation: Rotation,
pub metadata: &'a str,
pub integrity: f32,
pub seed: VarLong,
pub flags: Flags,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum Action {
UpdateData,
SaveStructure,
LoadStructure,
DetectSize,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum Mode {
Save,
Load,
Corner,
Data,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum Mirror {
None,
LeftRight,
FrontBack,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum Rotation {
None,
Clockwise90,
Clockwise180,
Counterclockwise90,
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
pub struct Flags {
pub ignore_entities: bool,
pub show_air: bool,
pub show_bounding_box: bool,
#[bits(5)]
_pad: u8,
}

View file

@ -0,0 +1,8 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct VehicleMoveC2s {
pub position: [f64; 3],
pub yaw: f32,
pub pitch: f32,
}

View file

@ -0,0 +1,6 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct QueryPingC2s {
pub payload: u64,
}

View file

@ -0,0 +1,4 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct QueryRequestC2s;

View file

@ -0,0 +1,367 @@
pub mod status {
pub use query_pong::QueryPongS2c;
pub use query_response::QueryResponseS2c;
pub mod query_pong;
pub mod query_response;
packet_group! {
#[derive(Clone)]
S2cStatusPacket<'a> {
0 = QueryResponseS2c<'a>,
1 = QueryPongS2c,
}
}
}
pub mod login {
pub use login_compression::LoginCompressionS2c;
pub use login_disconnect::LoginDisconnectS2c;
pub use login_hello::LoginHelloS2c;
pub use login_query_request::LoginQueryRequestS2c;
pub use login_success::LoginSuccessS2c;
pub mod login_compression;
pub mod login_disconnect;
pub mod login_hello;
pub mod login_query_request;
pub mod login_success;
packet_group! {
#[derive(Clone)]
S2cLoginPacket<'a> {
0 = LoginDisconnectS2c<'a>,
1 = LoginHelloS2c<'a>,
2 = LoginSuccessS2c<'a>,
3 = LoginCompressionS2c,
4 = LoginQueryRequestS2c<'a>,
}
}
}
pub mod play {
pub use advancement_update::AdvancementUpdateS2c;
pub use block_breaking_progress::BlockBreakingProgressS2c;
pub use block_entity_update::BlockEntityUpdateS2c;
pub use block_event::BlockEventS2c;
pub use block_update::BlockUpdateS2c;
pub use boss_bar::BossBarS2c;
pub use chat_message::ChatMessageS2c;
pub use chat_suggestions::ChatSuggestionsS2c;
pub use chunk_data::ChunkDataS2c;
pub use chunk_delta_update::ChunkDeltaUpdateS2c;
pub use chunk_load_distance::ChunkLoadDistanceS2c;
pub use chunk_render_distance_center::ChunkRenderDistanceCenterS2c;
pub use clear_titles::ClearTitlesS2c;
pub use close_screen::CloseScreenS2c;
pub use command_suggestions::CommandSuggestionsS2c;
pub use command_tree::CommandTreeS2c;
pub use cooldown_update::CooldownUpdateS2c;
pub use craft_failed_response::CraftFailedResponseS2c;
pub use custom_payload::CustomPayloadS2c;
pub use death_message::DeathMessageS2c;
pub use difficulty::DifficultyS2c;
pub use disconnect::DisconnectS2c;
pub use end_combat::EndCombatS2c;
pub use enter_combat::EnterCombatS2c;
pub use entities_destroy::EntitiesDestroyS2c;
pub use entity::{MoveRelativeS2c, RotateAndMoveRelativeS2c, RotateS2c};
pub use entity_animation::EntityAnimationS2c;
pub use entity_attach::EntityAttachS2c;
pub use entity_attributes::EntityAttributesS2c;
pub use entity_equipment_update::EntityEquipmentUpdateS2c;
pub use entity_passengers_set::EntityPassengersSetS2c;
pub use entity_position::EntityPositionS2c;
pub use entity_set_head_yaw::EntitySetHeadYawS2c;
pub use entity_spawn::EntitySpawnS2c;
pub use entity_status::EntityStatusS2c;
pub use entity_status_effect::EntityStatusEffectS2c;
pub use entity_tracker_update::EntityTrackerUpdateS2c;
pub use entity_velocity_update::EntityVelocityUpdateS2c;
pub use experience_bar_update::ExperienceBarUpdateS2c;
pub use experience_orb_spawn::ExperienceOrbSpawnS2c;
pub use explosion::ExplosionS2c;
pub use features::FeaturesS2c;
pub use game_join::GameJoinS2c;
pub use game_message::GameMessageS2c;
pub use game_state_change::GameStateChangeS2c;
pub use health_update::HealthUpdateS2c;
pub use inventory::InventoryS2c;
pub use item_pickup_animation::ItemPickupAnimationS2c;
pub use keep_alive::KeepAliveS2c;
pub use light_update::LightUpdateS2c;
pub use look_at::LookAtS2c;
pub use map_update::MapUpdateS2c;
pub use nbt_query_response::NbtQueryResponseS2c;
pub use open_horse_screen::OpenHorseScreenS2c;
pub use open_screen::OpenScreenS2c;
pub use open_written_book::OpenWrittenBookS2c;
pub use overlay_message::OverlayMessageS2c;
pub use particle::ParticleS2c;
pub use play_ping::PlayPingS2c;
pub use play_sound::PlaySoundS2c;
pub use play_sound_from_entity::PlaySoundFromEntityS2c;
pub use player_abilities::PlayerAbilitiesS2c;
pub use player_action_response::PlayerActionResponseS2c;
pub use player_list::PlayerListS2c;
pub use player_list_header::PlayerListHeaderS2c;
pub use player_position_look::PlayerPositionLookS2c;
pub use player_remove::PlayerRemoveS2c;
pub use player_respawn::PlayerRespawnS2c;
pub use player_spawn::PlayerSpawnS2c;
pub use player_spawn_position::PlayerSpawnPositionS2c;
pub use profileless_chat_message::ProfilelessChatMessageS2c;
pub use remove_entity_status_effect::RemoveEntityStatusEffectS2c;
pub use remove_message::RemoveMessageS2c;
pub use resource_pack_send::ResourcePackSendS2c;
pub use scoreboard_display::ScoreboardDisplayS2c;
pub use scoreboard_objective_update::ScoreboardObjectiveUpdateS2c;
pub use scoreboard_player_update::ScoreboardPlayerUpdateS2c;
pub use screen_handler_property_update::ScreenHandlerPropertyUpdateS2c;
pub use screen_handler_slot_update::ScreenHandlerSlotUpdateS2c;
pub use select_advancements_tab::SelectAdvancementsTabS2c;
pub use server_metadata::ServerMetadataS2c;
pub use set_camera_entity::SetCameraEntityS2c;
pub use set_trade_offers::SetTradeOffersS2c;
pub use sign_editor_open::SignEditorOpen;
pub use simulation_distance::SimulationDistanceS2c;
pub use statistics::StatisticsS2c;
pub use stop_sound::StopSoundS2c;
pub use subtitle::SubtitleS2c;
pub use synchronize_recipes::SynchronizeRecipesS2c;
pub use synchronize_tags::SynchronizeTagsS2c;
pub use team::TeamS2c;
pub use title::TitleS2c;
pub use title_fade::TitleFadeS2c;
pub use unload_chunk::UnloadChunkS2c;
pub use unlock_recipes::UnlockRecipesS2c;
pub use update_selected_slot::UpdateSelectedSlotS2c;
pub use vehicle_move::VehicleMoveS2c;
pub use world_border_center_changed::WorldBorderCenterChangedS2c;
pub use world_border_initialize::WorldBorderInitializeS2c;
pub use world_border_interpolate_size::WorldBorderInterpolateSizeS2c;
pub use world_border_size_changed::WorldBorderSizeChangedS2c;
pub use world_border_warning_blocks_changed::WorldBorderWarningBlocksChangedS2c;
pub use world_border_warning_time_changed::WorldBorderWarningTimeChangedS2c;
pub use world_event::WorldEventS2c;
pub use world_time_update::WorldTimeUpdateS2c;
pub mod advancement_update;
pub mod block_breaking_progress;
pub mod block_entity_update;
pub mod block_event;
pub mod block_update;
pub mod boss_bar;
pub mod chat_message;
pub mod chat_suggestions;
pub mod chunk_data;
pub mod chunk_delta_update;
pub mod chunk_load_distance;
pub mod chunk_render_distance_center;
pub mod clear_titles;
pub mod close_screen;
pub mod command_suggestions;
pub mod command_tree;
pub mod cooldown_update;
pub mod craft_failed_response;
pub mod custom_payload;
pub mod death_message;
pub mod difficulty;
pub mod disconnect;
pub mod end_combat;
pub mod enter_combat;
pub mod entities_destroy;
pub mod entity;
pub mod entity_animation;
pub mod entity_attach;
pub mod entity_attributes;
pub mod entity_equipment_update;
pub mod entity_passengers_set;
pub mod entity_position;
pub mod entity_set_head_yaw;
pub mod entity_spawn;
pub mod entity_status;
pub mod entity_status_effect;
pub mod entity_tracker_update;
pub mod entity_velocity_update;
pub mod experience_bar_update;
pub mod experience_orb_spawn;
pub mod explosion;
pub mod features;
pub mod game_join;
pub mod game_message;
pub mod game_state_change;
pub mod health_update;
pub mod inventory;
pub mod item_pickup_animation;
pub mod keep_alive;
pub mod light_update;
pub mod look_at;
pub mod map_update;
pub mod nbt_query_response;
pub mod open_horse_screen;
pub mod open_screen;
pub mod open_written_book;
pub mod overlay_message;
pub mod particle;
pub mod play_ping;
pub mod play_sound;
pub mod play_sound_from_entity;
pub mod player_abilities;
pub mod player_action_response;
pub mod player_list;
pub mod player_list_header;
pub mod player_position_look;
pub mod player_remove;
pub mod player_respawn;
pub mod player_spawn;
pub mod player_spawn_position;
pub mod profileless_chat_message;
pub mod remove_entity_status_effect;
pub mod remove_message;
pub mod resource_pack_send;
pub mod scoreboard_display;
pub mod scoreboard_objective_update;
pub mod scoreboard_player_update;
pub mod screen_handler_property_update;
pub mod screen_handler_slot_update;
pub mod select_advancements_tab;
pub mod server_metadata;
pub mod set_camera_entity;
pub mod set_trade_offers;
pub mod sign_editor_open;
pub mod simulation_distance;
pub mod statistics;
pub mod stop_sound;
pub mod subtitle;
pub mod synchronize_recipes;
pub mod synchronize_tags;
pub mod team;
pub mod title;
pub mod title_fade;
pub mod unload_chunk;
pub mod unlock_recipes;
pub mod update_selected_slot;
pub mod vehicle_move;
pub mod world_border_center_changed;
pub mod world_border_initialize;
pub mod world_border_interpolate_size;
pub mod world_border_size_changed;
pub mod world_border_warning_blocks_changed;
pub mod world_border_warning_time_changed;
pub mod world_event;
pub mod world_time_update;
packet_group! {
#[derive(Clone)]
S2cPlayPacket<'a> {
0 = EntitySpawnS2c,
1 = ExperienceOrbSpawnS2c,
2 = PlayerSpawnS2c,
3 = EntityAnimationS2c,
4 = StatisticsS2c,
5 = PlayerActionResponseS2c,
6 = BlockBreakingProgressS2c,
7 = BlockEntityUpdateS2c<'a>,
8 = BlockEventS2c,
9 = BlockUpdateS2c,
10 = BossBarS2c,
11 = DifficultyS2c,
12 = ClearTitlesS2c,
13 = CommandSuggestionsS2c<'a>,
14 = CommandTreeS2c<'a>,
15 = CloseScreenS2c,
16 = InventoryS2c<'a>,
17 = ScreenHandlerPropertyUpdateS2c,
18 = ScreenHandlerSlotUpdateS2c<'a>,
19 = CooldownUpdateS2c,
20 = ChatSuggestionsS2c<'a>,
21 = CustomPayloadS2c<'a>,
22 = RemoveMessageS2c<'a>,
23 = DisconnectS2c<'a>,
24 = ProfilelessChatMessageS2c<'a>,
25 = EntityStatusS2c,
26 = ExplosionS2c<'a>,
27 = UnloadChunkS2c,
28 = GameStateChangeS2c,
29 = OpenHorseScreenS2c,
30 = WorldBorderInitializeS2c,
31 = KeepAliveS2c,
32 = ChunkDataS2c<'a>,
33 = WorldEventS2c,
34 = LightUpdateS2c,
35 = ParticleS2c<'a>,
36 = GameJoinS2c<'a>,
37 = MapUpdateS2c<'a>,
38 = SetTradeOffersS2c,
39 = MoveRelativeS2c,
40 = RotateAndMoveRelativeS2c,
41 = RotateS2c,
42 = VehicleMoveS2c,
43 = OpenWrittenBookS2c,
44 = OpenScreenS2c<'a>,
45 = SignEditorOpen,
46 = PlayPingS2c,
47 = CraftFailedResponseS2c<'a>,
48 = PlayerAbilitiesS2c,
49 = ChatMessageS2c<'a>,
50 = EndCombatS2c,
51 = EnterCombatS2c,
52 = DeathMessageS2c<'a>,
53 = PlayerRemoveS2c<'a>,
54 = PlayerListS2c<'a>,
55 = LookAtS2c,
56 = PlayerPositionLookS2c,
57 = UnlockRecipesS2c<'a>,
58 = EntitiesDestroyS2c<'a>,
59 = RemoveEntityStatusEffectS2c,
60 = ResourcePackSendS2c<'a>,
61 = PlayerRespawnS2c<'a>,
62 = EntitySetHeadYawS2c,
63 = ChunkDeltaUpdateS2c<'a>,
64 = SelectAdvancementsTabS2c<'a>,
65 = ServerMetadataS2c<'a>,
66 = OverlayMessageS2c<'a>,
67 = WorldBorderCenterChangedS2c,
68 = WorldBorderInterpolateSizeS2c,
69 = WorldBorderSizeChangedS2c,
70 = WorldBorderWarningTimeChangedS2c,
71 = WorldBorderWarningBlocksChangedS2c,
72 = SetCameraEntityS2c,
73 = UpdateSelectedSlotS2c,
74 = ChunkRenderDistanceCenterS2c,
75 = ChunkLoadDistanceS2c,
76 = PlayerSpawnPositionS2c,
77 = ScoreboardDisplayS2c<'a>,
78 = EntityTrackerUpdateS2c<'a>,
79 = EntityAttachS2c,
80 = EntityVelocityUpdateS2c,
81 = EntityEquipmentUpdateS2c,
82 = ExperienceBarUpdateS2c,
83 = HealthUpdateS2c,
84 = ScoreboardObjectiveUpdateS2c<'a>,
85 = EntityPassengersSetS2c,
86 = TeamS2c<'a>,
87 = ScoreboardPlayerUpdateS2c<'a>,
88 = SimulationDistanceS2c,
89 = SubtitleS2c<'a>,
90 = WorldTimeUpdateS2c,
91 = TitleS2c<'a>,
92 = TitleFadeS2c,
93 = PlaySoundFromEntityS2c,
94 = PlaySoundS2c<'a>,
95 = StopSoundS2c<'a>,
96 = GameMessageS2c<'a>,
97 = PlayerListHeaderS2c<'a>,
98 = NbtQueryResponseS2c,
99 = ItemPickupAnimationS2c,
100 = EntityPositionS2c,
101 = AdvancementUpdateS2c<'a>,
102 = EntityAttributesS2c<'a>,
103 = FeaturesS2c<'a>,
104 = EntityStatusEffectS2c,
105 = SynchronizeRecipesS2c<'a>,
106 = SynchronizeTagsS2c<'a>,
}
}
}

View file

@ -0,0 +1,7 @@
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct LoginCompressionS2c {
pub threshold: VarInt,
}

View file

@ -0,0 +1,9 @@
use std::borrow::Cow;
use crate::text::Text;
use crate::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct LoginDisconnectS2c<'a> {
pub reason: Cow<'a, Text>,
}

View file

@ -0,0 +1,8 @@
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct LoginHelloS2c<'a> {
pub server_id: &'a str,
pub public_key: &'a [u8],
pub verify_token: &'a [u8],
}

View file

@ -0,0 +1,11 @@
use crate::ident::Ident;
use crate::raw_bytes::RawBytes;
use crate::var_int::VarInt;
use crate::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct LoginQueryRequestS2c<'a> {
pub message_id: VarInt,
pub channel: Ident<&'a str>,
pub data: RawBytes<'a>,
}

Some files were not shown because too many files have changed in this diff Show more