Move packets out of valence_core. (#335)

## Description

- Move all packets out of `valence_core` and into the places where
they're actually used. This has a few benefits:
- Avoids compiling code for packets that go unused when feature flags
are disabled.
- Code is distributed more uniformly across crates, improving
compilation times.
- Improves local reasoning when everything relevant to a module is
defined in the same place.
  - Easier to share code between the packet consumer and the packet.
- Tweak `Packet` macro syntax.
- Update `syn` to 2.0.
- Reorganize some code in `valence_client` (needs further work).
- Impl `WritePacket` for `Instance`.
- Remove packet enums such as `S2cPlayPacket` and `C2sPlayPacket`.
- Replace `assert_packet_count` and `assert_packet_order` macros with
non-macro methods.
To prevent this PR from getting out of hand, I've disabled the packet
inspector and stresser until they have been rewritten to account for
these changes.
This commit is contained in:
Ryan Johnson 2023-05-29 01:36:11 -07:00 committed by GitHub
parent e5de2d3f20
commit c5557e744d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
269 changed files with 5934 additions and 6043 deletions

View file

@ -1,6 +1,6 @@
[workspace] [workspace]
members = ["crates/*", "tools/*"] members = ["crates/*", "tools/*"]
exclude = ["rust-mc-bot"] exclude = ["rust-mc-bot", "tools/stresser", "tools/packet_inspector"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
@ -61,7 +61,7 @@ serde = "1.0.160"
serde_json = "1.0.96" serde_json = "1.0.96"
sha1 = "0.10.5" sha1 = "0.10.5"
sha2 = "0.10.6" sha2 = "0.10.6"
syn = "1.0.109" # TODO: update this. syn = "2.0.15"
syntect = { version = "5.0.0", default-features = false } syntect = { version = "5.0.0", default-features = false }
tempfile = "3.3.0" tempfile = "3.3.0"
thiserror = "1.0.40" thiserror = "1.0.40"

View file

@ -1,7 +1,7 @@
use std::hint::black_box; use std::hint::black_box;
use criterion::Criterion; use criterion::Criterion;
use valence::packet::{Decode, Encode}; use valence::protocol::{Decode, Encode};
pub fn decode_array(c: &mut Criterion) { pub fn decode_array(c: &mut Criterion) {
let floats = [123.0, 456.0, 789.0]; let floats = [123.0, 456.0, 789.0];

View file

@ -3,14 +3,16 @@ use std::hint::black_box;
use criterion::Criterion; use criterion::Criterion;
use valence::nbt::{compound, List}; use valence::nbt::{compound, List};
use valence::packet::array::LengthPrefixedArray;
use valence::packet::byte_angle::ByteAngle;
use valence::packet::decode::{decode_packet, PacketDecoder};
use valence::packet::encode::{encode_packet, encode_packet_compressed, PacketEncoder};
use valence::packet::s2c::play::{ChunkDataS2c, EntitySpawnS2c, PlayerListHeaderS2c};
use valence::packet::var_int::VarInt;
use valence::prelude::*; use valence::prelude::*;
use valence::protocol::array::LengthPrefixedArray;
use valence::protocol::byte_angle::ByteAngle;
use valence::protocol::decode::PacketDecoder;
use valence::protocol::encode::{encode_packet, encode_packet_compressed, PacketEncoder};
use valence::protocol::var_int::VarInt;
use valence::text::TextFormat; use valence::text::TextFormat;
use valence_entity::packet::EntitySpawnS2c;
use valence_instance::packet::ChunkDataS2c;
use valence_player_list::packet::PlayerListHeaderS2c;
pub fn packet(c: &mut Criterion) { pub fn packet(c: &mut Criterion) {
let mut encoder = PacketEncoder::new(); let mut encoder = PacketEncoder::new();
@ -135,7 +137,12 @@ pub fn packet(c: &mut Criterion) {
let decoder = black_box(&mut decoder); let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf); decoder.queue_slice(&packet_buf);
decode_packet::<ChunkDataS2c>(&decoder.try_next_packet().unwrap().unwrap()).unwrap(); decoder
.try_next_packet()
.unwrap()
.unwrap()
.decode::<ChunkDataS2c>()
.unwrap();
black_box(decoder); black_box(decoder);
}); });
@ -149,7 +156,11 @@ pub fn packet(c: &mut Criterion) {
let decoder = black_box(&mut decoder); let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf); decoder.queue_slice(&packet_buf);
decode_packet::<PlayerListHeaderS2c>(&decoder.try_next_packet().unwrap().unwrap()) decoder
.try_next_packet()
.unwrap()
.unwrap()
.decode::<PlayerListHeaderS2c>()
.unwrap(); .unwrap();
black_box(decoder); black_box(decoder);
@ -164,7 +175,12 @@ pub fn packet(c: &mut Criterion) {
let decoder = black_box(&mut decoder); let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf); decoder.queue_slice(&packet_buf);
decode_packet::<EntitySpawnS2c>(&decoder.try_next_packet().unwrap().unwrap()).unwrap(); decoder
.try_next_packet()
.unwrap()
.unwrap()
.decode::<EntitySpawnS2c>()
.unwrap();
black_box(decoder); black_box(decoder);
}); });
@ -182,7 +198,12 @@ pub fn packet(c: &mut Criterion) {
let decoder = black_box(&mut decoder); let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf); decoder.queue_slice(&packet_buf);
decode_packet::<ChunkDataS2c>(&decoder.try_next_packet().unwrap().unwrap()).unwrap(); decoder
.try_next_packet()
.unwrap()
.unwrap()
.decode::<ChunkDataS2c>()
.unwrap();
black_box(decoder); black_box(decoder);
}); });
@ -202,7 +223,11 @@ pub fn packet(c: &mut Criterion) {
let decoder = black_box(&mut decoder); let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf); decoder.queue_slice(&packet_buf);
decode_packet::<PlayerListHeaderS2c>(&decoder.try_next_packet().unwrap().unwrap()) decoder
.try_next_packet()
.unwrap()
.unwrap()
.decode::<PlayerListHeaderS2c>()
.unwrap(); .unwrap();
black_box(decoder); black_box(decoder);
@ -217,7 +242,12 @@ pub fn packet(c: &mut Criterion) {
let decoder = black_box(&mut decoder); let decoder = black_box(&mut decoder);
decoder.queue_slice(&packet_buf); decoder.queue_slice(&packet_buf);
decode_packet::<EntitySpawnS2c>(&decoder.try_next_packet().unwrap().unwrap()).unwrap(); decoder
.try_next_packet()
.unwrap()
.unwrap()
.decode::<EntitySpawnS2c>()
.unwrap();
black_box(decoder); black_box(decoder);
}); });

View file

@ -2,8 +2,8 @@ use std::hint::black_box;
use criterion::Criterion; use criterion::Criterion;
use rand::Rng; use rand::Rng;
use valence::packet::var_int::VarInt; use valence::protocol::var_int::VarInt;
use valence::packet::{Decode, Encode}; use valence::protocol::{Decode, Encode};
pub fn var_int(c: &mut Criterion) { pub fn var_int(c: &mut Criterion) {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();

View file

@ -2,8 +2,8 @@ use std::hint::black_box;
use criterion::Criterion; use criterion::Criterion;
use rand::Rng; use rand::Rng;
use valence::packet::var_long::VarLong; use valence::protocol::var_long::VarLong;
use valence::packet::{Decode, Encode}; use valence::protocol::{Decode, Encode};
pub fn var_long(c: &mut Criterion) { pub fn var_long(c: &mut Criterion) {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();

View file

@ -1,8 +1,9 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::misc::{ChatMessage, InteractBlock};
use valence::nbt::{compound, List}; use valence::nbt::{compound, List};
use valence::prelude::*; use valence::prelude::*;
use valence_client::chat::ChatMessageEvent;
use valence_client::interact_block::InteractBlockEvent;
const FLOOR_Y: i32 = 64; const FLOOR_Y: i32 = 64;
const SIGN_POS: [i32; 3] = [3, FLOOR_Y + 1, 2]; const SIGN_POS: [i32; 3] = [3, FLOOR_Y + 1, 2];
@ -74,12 +75,12 @@ fn init_clients(
fn event_handler( fn event_handler(
clients: Query<(&Username, &Properties, &UniqueId)>, clients: Query<(&Username, &Properties, &UniqueId)>,
mut messages: EventReader<ChatMessage>, mut messages: EventReader<ChatMessageEvent>,
mut block_interacts: EventReader<InteractBlock>, mut block_interacts: EventReader<InteractBlockEvent>,
mut instances: Query<&mut Instance>, mut instances: Query<&mut Instance>,
) { ) {
let mut instance = instances.single_mut(); let mut instance = instances.single_mut();
for ChatMessage { for ChatMessageEvent {
client, message, .. client, message, ..
} in messages.iter() } in messages.iter()
{ {
@ -93,7 +94,7 @@ fn event_handler(
nbt.insert("Text3", format!("~{}", username).italic()); nbt.insert("Text3", format!("~{}", username).italic());
} }
for InteractBlock { for InteractBlockEvent {
client, client,
position, position,
hand, hand,

View file

@ -1,8 +1,8 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::misc::InteractBlock;
use valence::inventory::ClientInventoryState; use valence::inventory::ClientInventoryState;
use valence::prelude::*; use valence::prelude::*;
use valence_client::interact_block::InteractBlockEvent;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -77,7 +77,7 @@ fn toggle_gamemode_on_sneak(mut clients: Query<&mut GameMode>, mut events: Event
fn digging_creative_mode( fn digging_creative_mode(
clients: Query<&GameMode>, clients: Query<&GameMode>,
mut instances: Query<&mut Instance>, mut instances: Query<&mut Instance>,
mut events: EventReader<Digging>, mut events: EventReader<DiggingEvent>,
) { ) {
let mut instance = instances.single_mut(); let mut instance = instances.single_mut();
@ -94,7 +94,7 @@ fn digging_creative_mode(
fn digging_survival_mode( fn digging_survival_mode(
clients: Query<&GameMode>, clients: Query<&GameMode>,
mut instances: Query<&mut Instance>, mut instances: Query<&mut Instance>,
mut events: EventReader<Digging>, mut events: EventReader<DiggingEvent>,
) { ) {
let mut instance = instances.single_mut(); let mut instance = instances.single_mut();
@ -111,7 +111,7 @@ fn digging_survival_mode(
fn place_blocks( fn place_blocks(
mut clients: Query<(&mut Inventory, &GameMode, &ClientInventoryState)>, mut clients: Query<(&mut Inventory, &GameMode, &ClientInventoryState)>,
mut instances: Query<&mut Instance>, mut instances: Query<&mut Instance>,
mut events: EventReader<InteractBlock>, mut events: EventReader<InteractBlockEvent>,
) { ) {
let mut instance = instances.single_mut(); let mut instance = instances.single_mut();

View file

@ -1,7 +1,7 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::misc::InteractBlock;
use valence::prelude::*; use valence::prelude::*;
use valence_client::interact_block::InteractBlockEvent;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
const CHEST_POS: [i32; 3] = [0, SPAWN_Y + 1, 3]; const CHEST_POS: [i32; 3] = [0, SPAWN_Y + 1, 3];
@ -78,7 +78,7 @@ fn toggle_gamemode_on_sneak(mut clients: Query<&mut GameMode>, mut events: Event
fn open_chest( fn open_chest(
mut commands: Commands, mut commands: Commands,
inventories: Query<Entity, (With<Inventory>, Without<Client>)>, inventories: Query<Entity, (With<Inventory>, Without<Client>)>,
mut events: EventReader<InteractBlock>, mut events: EventReader<InteractBlockEvent>,
) { ) {
for event in events.iter() { for event in events.iter() {
if event.position != CHEST_POS.into() { if event.position != CHEST_POS.into() {

View file

@ -96,7 +96,7 @@ fn handle_combat_events(
server: Res<Server>, server: Res<Server>,
mut clients: Query<CombatQuery>, mut clients: Query<CombatQuery>,
mut sprinting: EventReader<Sprinting>, mut sprinting: EventReader<Sprinting>,
mut interact_entity: EventReader<InteractEntity>, mut interact_entity: EventReader<InteractEntityEvent>,
) { ) {
for &Sprinting { client, state } in sprinting.iter() { for &Sprinting { client, state } in sprinting.iter() {
if let Ok(mut client) = clients.get_mut(client) { if let Ok(mut client) = clients.get_mut(client) {
@ -104,7 +104,7 @@ fn handle_combat_events(
} }
} }
for &InteractEntity { for &InteractEntityEvent {
client: attacker_client, client: attacker_client,
entity: victim_client, entity: victim_client,
.. ..

View file

@ -152,7 +152,7 @@ impl LifeBoard {
} }
} }
fn toggle_cell_on_dig(mut events: EventReader<Digging>, mut board: ResMut<LifeBoard>) { fn toggle_cell_on_dig(mut events: EventReader<DiggingEvent>, mut board: ResMut<LifeBoard>) {
for event in events.iter() { for event in events.iter() {
if event.state == DiggingState::Start { if event.state == DiggingState::Start {
let (x, z) = (event.position.x, event.position.z); let (x, z) = (event.position.x, event.position.z);

View file

@ -1,7 +1,7 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::misc::Respawn;
use valence::prelude::*; use valence::prelude::*;
use valence_client::status::RequestRespawnEvent;
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -76,7 +76,7 @@ fn squat_and_die(mut clients: Query<&mut Client>, mut events: EventReader<Sneaki
fn necromancy( fn necromancy(
mut clients: Query<(&mut Position, &mut Look, &mut Location)>, mut clients: Query<(&mut Position, &mut Look, &mut Location)>,
mut events: EventReader<Respawn>, mut events: EventReader<RequestRespawnEvent>,
instances: Query<Entity, With<Instance>>, instances: Query<Entity, With<Instance>>,
) { ) {
for event in events.iter() { for event in events.iter() {

View file

@ -5,9 +5,8 @@ use std::time::{SystemTime, UNIX_EPOCH};
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use rand::Rng; use rand::Rng;
use valence::packet::s2c::play::TitleFadeS2c;
use valence::prelude::*; use valence::prelude::*;
use valence::sound::{Sound, SoundCategory}; use valence::protocol::packet::sound::{Sound, SoundCategory};
const START_POS: BlockPos = BlockPos::new(0, 100, 0); const START_POS: BlockPos = BlockPos::new(0, 100, 0);
const VIEW_DIST: u8 = 10; const VIEW_DIST: u8 = 10;
@ -179,15 +178,8 @@ fn manage_blocks(mut clients: Query<(&mut Client, &Position, &mut GameState, &mu
pitch, pitch,
); );
client.set_title( client.set_title("");
"", client.set_subtitle(state.score.to_string().color(Color::LIGHT_PURPLE).bold());
state.score.to_string().color(Color::LIGHT_PURPLE).bold(),
TitleFadeS2c {
fade_in: 0,
stay: 7,
fade_out: 4,
},
);
} }
} }
} }

View file

@ -1,10 +1,9 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use valence::client::misc::{ResourcePackStatus, ResourcePackStatusChange};
use valence::entity::player::PlayerEntityBundle; use valence::entity::player::PlayerEntityBundle;
use valence::entity::sheep::SheepEntityBundle; use valence::entity::sheep::SheepEntityBundle;
use valence::packet::c2s::play::player_interact_entity::EntityInteraction;
use valence::prelude::*; use valence::prelude::*;
use valence_client::resource_pack::{ResourcePackStatus, ResourcePackStatusEvent};
const SPAWN_Y: i32 = 64; const SPAWN_Y: i32 = 64;
@ -69,7 +68,7 @@ fn init_clients(
} }
} }
fn prompt_on_punch(mut clients: Query<&mut Client>, mut events: EventReader<InteractEntity>) { fn prompt_on_punch(mut clients: Query<&mut Client>, mut events: EventReader<InteractEntityEvent>) {
for event in events.iter() { for event in events.iter() {
if let Ok(mut client) = clients.get_mut(event.client) { if let Ok(mut client) = clients.get_mut(event.client) {
if event.interact == EntityInteraction::Attack { if event.interact == EntityInteraction::Attack {
@ -86,7 +85,7 @@ fn prompt_on_punch(mut clients: Query<&mut Client>, mut events: EventReader<Inte
fn on_resource_pack_status( fn on_resource_pack_status(
mut clients: Query<&mut Client>, mut clients: Query<&mut Client>,
mut events: EventReader<ResourcePackStatusChange>, mut events: EventReader<ResourcePackStatusEvent>,
) { ) {
for event in events.iter() { for event in events.iter() {
if let Ok(mut client) = clients.get_mut(event.client) { if let Ok(mut client) = clients.get_mut(event.client) {
@ -100,7 +99,7 @@ fn on_resource_pack_status(
ResourcePackStatus::FailedDownload => { ResourcePackStatus::FailedDownload => {
client.send_message("Resource pack failed to download.".color(Color::RED)); client.send_message("Resource pack failed to download.".color(Color::RED));
} }
ResourcePackStatus::Loaded => { ResourcePackStatus::SuccessfullyLoaded => {
client client
.send_message("Resource pack successfully downloaded.".color(Color::BLUE)); .send_message("Resource pack successfully downloaded.".color(Color::BLUE));
} }

View file

@ -26,6 +26,8 @@ use bevy_app::{PluginGroup, PluginGroupBuilder};
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
#[cfg(feature = "advancement")]
pub use valence_advancement as advancement;
#[cfg(feature = "anvil")] #[cfg(feature = "anvil")]
pub use valence_anvil as anvil; pub use valence_anvil as anvil;
pub use valence_core::*; pub use valence_core::*;
@ -64,10 +66,11 @@ pub mod prelude {
pub use client::command::*; pub use client::command::*;
pub use client::event_loop::{EventLoopSchedule, EventLoopSet}; pub use client::event_loop::{EventLoopSchedule, EventLoopSet};
pub use client::interact_entity::*; pub use client::interact_entity::*;
pub use client::title::SetTitle as _;
pub use client::{ pub use client::{
despawn_disconnected_clients, Client, CompassPos, DeathLocation, HasRespawnScreen, despawn_disconnected_clients, Client, CompassPos, DeathLocation, HasRespawnScreen,
HashedSeed, Ip, IsDebug, IsFlat, IsHardcore, OldView, OldViewDistance, OpLevel, HashedSeed, Ip, IsDebug, IsFlat, IsHardcore, OldView, OldViewDistance, PrevGameMode,
PrevGameMode, Properties, ReducedDebugInfo, Username, View, ViewDistance, Properties, ReducedDebugInfo, Username, View, ViewDistance,
}; };
pub use despawn::Despawned; pub use despawn::Despawned;
pub use dimension::{DimensionType, DimensionTypeRegistry}; pub use dimension::{DimensionType, DimensionTypeRegistry};
@ -93,7 +96,7 @@ pub mod prelude {
ErasedNetworkCallbacks, NetworkCallbacks, NetworkSettings, NewClientInfo, ErasedNetworkCallbacks, NetworkCallbacks, NetworkSettings, NewClientInfo,
SharedNetworkState, SharedNetworkState,
}; };
pub use packet::s2c::play::particle::Particle; pub use particle::Particle;
#[cfg(feature = "player_list")] #[cfg(feature = "player_list")]
pub use player_list::{PlayerList, PlayerListEntry}; pub use player_list::{PlayerList, PlayerListEntry};
pub use text::{Color, Text, TextFormat}; pub use text::{Color, Text, TextFormat};

View file

@ -8,11 +8,10 @@ use bevy_ecs::schedule::{LogLevel, ScheduleBuildSettings};
use bytes::{Buf, BufMut, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use uuid::Uuid; use uuid::Uuid;
use valence_client::ClientBundleArgs; use valence_client::ClientBundleArgs;
use valence_core::packet::decode::{decode_packet, PacketDecoder}; use valence_core::protocol::decode::{PacketDecoder, PacketFrame};
use valence_core::packet::encode::PacketEncoder; use valence_core::protocol::encode::PacketEncoder;
use valence_core::packet::s2c::play::S2cPlayPacket; use valence_core::protocol::var_int::VarInt;
use valence_core::packet::var_int::VarInt; use valence_core::protocol::{Encode, Packet};
use valence_core::packet::Packet;
use valence_core::{ident, CoreSettings, Server}; use valence_core::{ident, CoreSettings, Server};
use valence_entity::Location; use valence_entity::Location;
use valence_network::{ConnectionMode, NetworkSettings}; use valence_network::{ConnectionMode, NetworkSettings};
@ -117,7 +116,7 @@ impl MockClientConnection {
.push_back(ReceivedPacket { .push_back(ReceivedPacket {
timestamp: Instant::now(), timestamp: Instant::now(),
id, id,
data: bytes.freeze(), body: bytes.freeze(),
}); });
} }
@ -151,7 +150,6 @@ struct MockClientHelper {
conn: MockClientConnection, conn: MockClientConnection,
dec: PacketDecoder, dec: PacketDecoder,
scratch: BytesMut, scratch: BytesMut,
collected_frames: Vec<BytesMut>,
} }
impl MockClientHelper { impl MockClientHelper {
@ -160,38 +158,39 @@ impl MockClientHelper {
conn, conn,
dec: PacketDecoder::new(), dec: PacketDecoder::new(),
scratch: BytesMut::new(), scratch: BytesMut::new(),
collected_frames: vec![],
} }
} }
/// Inject a packet to be treated as a packet inbound to the server. Panics /// Inject a packet to be treated as a packet inbound to the server. Panics
/// if the packet cannot be sent. /// if the packet cannot be sent.
fn send<'a>(&mut self, packet: &impl Packet<'a>) { #[track_caller]
fn send<P>(&mut self, packet: &P)
where
P: Packet + Encode,
{
packet packet
.encode_packet((&mut self.scratch).writer()) .encode_with_id((&mut self.scratch).writer())
.expect("failed to encode packet"); .expect("failed to encode packet");
self.conn.inject_recv(self.scratch.split()); self.conn.inject_recv(self.scratch.split());
} }
/// Collect all packets that have been sent to the client. /// Collect all packets that have been sent to the client.
fn collect_sent(&mut self) -> Vec<S2cPlayPacket> { #[track_caller]
fn collect_sent(&mut self) -> PacketFrames {
self.dec.queue_bytes(self.conn.take_sent()); self.dec.queue_bytes(self.conn.take_sent());
self.collected_frames.clear(); let mut res = vec![];
while let Some(frame) = self while let Some(frame) = self
.dec .dec
.try_next_packet() .try_next_packet()
.expect("failed to decode packet frame") .expect("failed to decode packet frame")
{ {
self.collected_frames.push(frame); res.push(frame);
} }
self.collected_frames PacketFrames(res)
.iter()
.map(|frame| decode_packet(frame).expect("failed to decode packet"))
.collect()
} }
fn clear_sent(&mut self) { fn clear_sent(&mut self) {
@ -199,36 +198,87 @@ impl MockClientHelper {
} }
} }
macro_rules! assert_packet_order { struct PacketFrames(Vec<PacketFrame>);
($sent_packets:ident, $($packets:pat),+) => {{
let sent_packets: &Vec<valence_core::packet::s2c::play::S2cPlayPacket> = &$sent_packets; impl PacketFrames {
let positions = [ #[track_caller]
$((sent_packets.iter().position(|p| matches!(p, $packets))),)* fn assert_count<P: Packet>(&self, expected_count: usize) {
]; let actual_count = self.0.iter().filter(|f| f.id == P::ID).count();
assert!(positions.windows(2).all(|w: &[Option<usize>]| w[0] < w[1]));
}}; assert_eq!(
expected_count,
actual_count,
"unexpected packet count for {} (expected {expected_count}, got {actual_count})",
P::NAME
);
}
#[track_caller]
fn assert_order<L: PacketList>(&self) {
let positions: Vec<_> = self
.0
.iter()
.filter_map(|f| L::packets().iter().position(|(id, _)| f.id == *id))
.collect();
// TODO: replace with slice::is_sorted.
let is_sorted = positions.windows(2).all(|w| w[0] <= w[1]);
assert!(
is_sorted,
"packets out of order (expected {:?}, got {:?})",
L::packets(),
self.debug::<L>()
);
}
fn debug<L: PacketList>(&self) -> impl std::fmt::Debug {
self.0
.iter()
.map(|f| {
L::packets()
.iter()
.find(|(id, _)| f.id == *id)
.cloned()
.unwrap_or((f.id, "<ignored>"))
})
.collect::<Vec<_>>()
}
} }
macro_rules! assert_packet_count { trait PacketList {
($sent_packets:ident, $count:tt, $packet:pat) => {{ fn packets() -> &'static [(i32, &'static str)];
let sent_packets: &Vec<valence_core::packet::s2c::play::S2cPlayPacket> = &$sent_packets;
let count = sent_packets.iter().filter(|p| matches!(p, $packet)).count();
assert_eq!(
count,
$count,
"expected {} {} packets, got {}\nPackets actually found:\n[\n\t{}\n]\n",
$count,
stringify!($packet),
count,
sent_packets
.iter()
.map(|p| format!("{:?}", p))
.collect::<Vec<_>>()
.join(",\n\t")
);
}};
} }
macro_rules! impl_packet_list {
($($ty:ident),*) => {
impl<$($ty: Packet,)*> PacketList for ($($ty,)*) {
fn packets() -> &'static [(i32, &'static str)] {
&[
$(
(
$ty::ID,
$ty::NAME
),
)*
]
}
}
}
}
impl_packet_list!(A);
impl_packet_list!(A, B);
impl_packet_list!(A, B, C);
impl_packet_list!(A, B, C, D);
impl_packet_list!(A, B, C, D, E);
impl_packet_list!(A, B, C, D, E, F);
impl_packet_list!(A, B, C, D, E, F, G);
impl_packet_list!(A, B, C, D, E, F, G, H);
impl_packet_list!(A, B, C, D, E, F, G, H, I);
impl_packet_list!(A, B, C, D, E, F, G, H, I, J);
impl_packet_list!(A, B, C, D, E, F, G, H, I, J, K);
mod client; mod client;
mod example; mod example;
mod inventory; mod inventory;

View file

@ -4,8 +4,8 @@ use bevy_app::App;
use bevy_ecs::world::EntityMut; use bevy_ecs::world::EntityMut;
use valence_client::ViewDistance; use valence_client::ViewDistance;
use valence_core::chunk_pos::ChunkView; use valence_core::chunk_pos::ChunkView;
use valence_core::packet::s2c::play::{ChunkDataS2c, S2cPlayPacket, UnloadChunkS2c};
use valence_entity::Position; use valence_entity::Position;
use valence_instance::packet::{ChunkDataS2c, UnloadChunkS2c};
use valence_instance::Chunk; use valence_instance::Chunk;
use super::*; use super::*;
@ -38,8 +38,9 @@ fn client_chunk_view_change() {
let mut loaded_chunks = BTreeSet::new(); let mut loaded_chunks = BTreeSet::new();
for pkt in client_helper.collect_sent() { for f in client_helper.collect_sent().0 {
if let S2cPlayPacket::ChunkDataS2c(ChunkDataS2c { pos, .. }) = pkt { if f.id == ChunkDataS2c::ID {
let ChunkDataS2c { pos, .. } = f.decode::<ChunkDataS2c>().unwrap();
assert!(loaded_chunks.insert(pos), "({pos:?})"); assert!(loaded_chunks.insert(pos), "({pos:?})");
} }
} }
@ -57,12 +58,14 @@ fn client_chunk_view_change() {
app.update(); app.update();
let client = app.world.entity_mut(client_ent); let client = app.world.entity_mut(client_ent);
for pkt in client_helper.collect_sent() { for f in client_helper.collect_sent().0 {
match pkt { match f.id {
S2cPlayPacket::ChunkDataS2c(ChunkDataS2c { pos, .. }) => { ChunkDataS2c::ID => {
let ChunkDataS2c { pos, .. } = f.decode().unwrap();
assert!(loaded_chunks.insert(pos), "({pos:?})"); assert!(loaded_chunks.insert(pos), "({pos:?})");
} }
S2cPlayPacket::UnloadChunkS2c(UnloadChunkS2c { pos }) => { UnloadChunkS2c::ID => {
let UnloadChunkS2c { pos } = f.decode().unwrap();
assert!(loaded_chunks.remove(&pos), "({pos:?})"); assert!(loaded_chunks.remove(&pos), "({pos:?})");
} }
_ => {} _ => {}

View file

@ -6,8 +6,8 @@
//! Some of the tests in this file may be inferior duplicates of real tests. //! Some of the tests in this file may be inferior duplicates of real tests.
use bevy_app::App; use bevy_app::App;
use valence_core::packet::c2s::play::PositionAndOnGround; use valence_client::movement::PositionAndOnGroundC2s;
use valence_core::packet::s2c::play::S2cPlayPacket; use valence_inventory::packet::{InventoryS2c, OpenScreenS2c};
use super::*; use super::*;
use crate::prelude::*; use crate::prelude::*;
@ -35,7 +35,7 @@ fn example_test_client_position() {
let (client_ent, mut client_helper) = scenario_single_client(&mut app); let (client_ent, mut client_helper) = scenario_single_client(&mut app);
// Send a packet as the client to the server. // Send a packet as the client to the server.
let packet = PositionAndOnGround { let packet = PositionAndOnGroundC2s {
position: DVec3::new(12.0, 64.0, 0.0), position: DVec3::new(12.0, 64.0, 0.0),
on_ground: true, on_ground: true,
}; };
@ -76,13 +76,11 @@ fn example_test_open_inventory() {
app.world app.world
.get::<Client>(client_ent) .get::<Client>(client_ent)
.expect("client not found"); .expect("client not found");
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreenS2c(_)); sent_packets.assert_count::<OpenScreenS2c>(1);
assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); sent_packets.assert_count::<InventoryS2c>(1);
assert_packet_order!(
sent_packets, sent_packets.assert_order::<(OpenScreenS2c, InventoryS2c)>();
S2cPlayPacket::OpenScreenS2c(_),
S2cPlayPacket::InventoryS2c(_)
);
} }

View file

@ -1,9 +1,10 @@
use bevy_app::App; use bevy_app::App;
use valence_core::game_mode::GameMode; use valence_core::game_mode::GameMode;
use valence_core::item::{ItemKind, ItemStack}; use valence_core::item::{ItemKind, ItemStack};
use valence_core::packet::c2s::play::click_slot::{ClickMode, Slot}; use valence_inventory::packet::{
use valence_core::packet::c2s::play::ClickSlotC2s; ClickMode, ClickSlotC2s, CloseScreenS2c, CreativeInventoryActionC2s, InventoryS2c,
use valence_core::packet::s2c::play::S2cPlayPacket; OpenScreenS2c, ScreenHandlerSlotUpdateS2c, SlotChange, UpdateSelectedSlotC2s,
};
use valence_inventory::{ use valence_inventory::{
convert_to_player_slot_id, ClientInventoryState, CursorItem, DropItemStack, Inventory, convert_to_player_slot_id, ClientInventoryState, CursorItem, DropItemStack, Inventory,
InventoryKind, OpenInventory, InventoryKind, OpenInventory,
@ -35,13 +36,9 @@ fn test_should_open_inventory() {
// Make assertions // Make assertions
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
assert_packet_count!(sent_packets, 1, S2cPlayPacket::OpenScreenS2c(_)); sent_packets.assert_count::<OpenScreenS2c>(1);
assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); sent_packets.assert_count::<InventoryS2c>(1);
assert_packet_order!( sent_packets.assert_order::<(OpenScreenS2c, InventoryS2c)>();
sent_packets,
S2cPlayPacket::OpenScreenS2c(_),
S2cPlayPacket::InventoryS2c(_)
);
} }
#[test] #[test]
@ -77,7 +74,7 @@ fn test_should_close_inventory() {
// Make assertions // Make assertions
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseScreenS2c(_)); sent_packets.assert_count::<CloseScreenS2c>(1);
} }
#[test] #[test]
@ -109,8 +106,9 @@ fn test_should_remove_invalid_open_inventory() {
// Make assertions // Make assertions
assert!(app.world.get::<OpenInventory>(client_ent).is_none()); assert!(app.world.get::<OpenInventory>(client_ent).is_none());
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
assert_packet_count!(sent_packets, 1, S2cPlayPacket::CloseScreenS2c(_)); sent_packets.assert_count::<CloseScreenS2c>(1);
} }
#[test] #[test]
@ -134,13 +132,14 @@ fn test_should_modify_player_inventory_click_slot() {
.get::<ClientInventoryState>(client_ent) .get::<ClientInventoryState>(client_ent)
.unwrap() .unwrap()
.state_id(); .state_id();
client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s {
client_helper.send(&ClickSlotC2s {
window_id: 0, window_id: 0,
button: 0, button: 0,
mode: valence_core::packet::c2s::play::click_slot::ClickMode::Click, mode: ClickMode::Click,
state_id: VarInt(state_id.0), state_id: VarInt(state_id.0),
slot_idx: 20, slot_idx: 20,
slot_changes: vec![valence_core::packet::c2s::play::click_slot::Slot { slot_changes: vec![SlotChange {
idx: 20, idx: 20,
item: None, item: None,
}], }],
@ -155,20 +154,21 @@ fn test_should_modify_player_inventory_click_slot() {
// because the inventory was changed as a result of the client's click, the // because the inventory was changed as a result of the client's click, the
// server should not send any packets to the client because the client // server should not send any packets to the client because the client
// already knows about the change. // already knows about the change.
assert_packet_count!( sent_packets.assert_count::<InventoryS2c>(0);
sent_packets, sent_packets.assert_count::<ScreenHandlerSlotUpdateS2c>(0);
0,
S2cPlayPacket::InventoryS2c(_) | S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
let inventory = app let inventory = app
.world .world
.get::<Inventory>(client_ent) .get::<Inventory>(client_ent)
.expect("could not find inventory for client"); .expect("could not find inventory for client");
assert_eq!(inventory.slot(20), None); assert_eq!(inventory.slot(20), None);
let cursor_item = app let cursor_item = app
.world .world
.get::<CursorItem>(client_ent) .get::<CursorItem>(client_ent)
.expect("could not find client"); .expect("could not find client");
assert_eq!( assert_eq!(
cursor_item.0, cursor_item.0,
Some(ItemStack::new(ItemKind::Diamond, 2, None)) Some(ItemStack::new(ItemKind::Diamond, 2, None))
@ -205,11 +205,7 @@ fn test_should_modify_player_inventory_server_side() {
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
// because the inventory was modified server side, the client needs to be // because the inventory was modified server side, the client needs to be
// updated with the change. // updated with the change.
assert_packet_count!( sent_packets.assert_count::<ScreenHandlerSlotUpdateS2c>(1);
sent_packets,
1,
S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
} }
#[test] #[test]
@ -231,7 +227,7 @@ fn test_should_sync_entire_player_inventory() {
// Make assertions // Make assertions
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); sent_packets.assert_count::<InventoryS2c>(1);
} }
fn set_up_open_inventory(app: &mut App, client_ent: Entity) -> Entity { fn set_up_open_inventory(app: &mut App, client_ent: Entity) -> Entity {
@ -267,13 +263,13 @@ fn test_should_modify_open_inventory_click_slot() {
let inv_state = app.world.get::<ClientInventoryState>(client_ent).unwrap(); let inv_state = app.world.get::<ClientInventoryState>(client_ent).unwrap();
let state_id = inv_state.state_id(); let state_id = inv_state.state_id();
let window_id = inv_state.window_id(); let window_id = inv_state.window_id();
client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { client_helper.send(&ClickSlotC2s {
window_id, window_id,
button: 0,
mode: valence_core::packet::c2s::play::click_slot::ClickMode::Click,
state_id: VarInt(state_id.0), state_id: VarInt(state_id.0),
slot_idx: 20, slot_idx: 20,
slot_changes: vec![valence_core::packet::c2s::play::click_slot::Slot { button: 0,
mode: ClickMode::Click,
slot_changes: vec![SlotChange {
idx: 20, idx: 20,
item: None, item: None,
}], }],
@ -288,11 +284,9 @@ fn test_should_modify_open_inventory_click_slot() {
// because the inventory was modified as a result of the client's click, the // because the inventory was modified as a result of the client's click, the
// server should not send any packets to the client because the client // server should not send any packets to the client because the client
// already knows about the change. // already knows about the change.
assert_packet_count!( sent_packets.assert_count::<InventoryS2c>(0);
sent_packets, sent_packets.assert_count::<ScreenHandlerSlotUpdateS2c>(0);
0,
S2cPlayPacket::InventoryS2c(_) | S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
let inventory = app let inventory = app
.world .world
.get::<Inventory>(inventory_ent) .get::<Inventory>(inventory_ent)
@ -332,15 +326,13 @@ fn test_should_modify_open_inventory_server_side() {
// because the inventory was modified server side, the client needs to be // because the inventory was modified server side, the client needs to be
// updated with the change. // updated with the change.
assert_packet_count!( sent_packets.assert_count::<ScreenHandlerSlotUpdateS2c>(1);
sent_packets,
1,
S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
let inventory = app let inventory = app
.world .world
.get::<Inventory>(inventory_ent) .get::<Inventory>(inventory_ent)
.expect("could not find inventory for client"); .expect("could not find inventory for client");
assert_eq!( assert_eq!(
inventory.slot(5), inventory.slot(5),
Some(&ItemStack::new(ItemKind::IronIngot, 1, None)) Some(&ItemStack::new(ItemKind::IronIngot, 1, None))
@ -367,7 +359,7 @@ fn test_should_sync_entire_open_inventory() {
// Make assertions // Make assertions
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
assert_packet_count!(sent_packets, 1, S2cPlayPacket::InventoryS2c(_)); sent_packets.assert_count::<InventoryS2c>(1);
} }
#[test] #[test]
@ -384,12 +376,10 @@ fn test_set_creative_mode_slot_handling() {
app.update(); app.update();
client_helper.clear_sent(); client_helper.clear_sent();
client_helper.send( client_helper.send(&CreativeInventoryActionC2s {
&valence_core::packet::c2s::play::CreativeInventoryActionC2s {
slot: 36, slot: 36,
clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)),
}, });
);
app.update(); app.update();
@ -398,6 +388,7 @@ fn test_set_creative_mode_slot_handling() {
.world .world
.get::<Inventory>(client_ent) .get::<Inventory>(client_ent)
.expect("could not find inventory for client"); .expect("could not find inventory for client");
assert_eq!( assert_eq!(
inventory.slot(36), inventory.slot(36),
Some(&ItemStack::new(ItemKind::Diamond, 2, None)) Some(&ItemStack::new(ItemKind::Diamond, 2, None))
@ -418,12 +409,10 @@ fn test_ignore_set_creative_mode_slot_if_not_creative() {
app.update(); app.update();
client_helper.clear_sent(); client_helper.clear_sent();
client_helper.send( client_helper.send(&CreativeInventoryActionC2s {
&valence_core::packet::c2s::play::CreativeInventoryActionC2s {
slot: 36, slot: 36,
clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)), clicked_item: Some(ItemStack::new(ItemKind::Diamond, 2, None)),
}, });
);
app.update(); app.update();
@ -480,7 +469,7 @@ fn test_should_handle_set_held_item() {
app.update(); app.update();
client_helper.clear_sent(); client_helper.clear_sent();
client_helper.send(&valence_core::packet::c2s::play::UpdateSelectedSlotC2s { slot: 4 }); client_helper.send(&UpdateSelectedSlotC2s { slot: 4 });
app.update(); app.update();
@ -489,6 +478,7 @@ fn test_should_handle_set_held_item() {
.world .world
.get::<ClientInventoryState>(client_ent) .get::<ClientInventoryState>(client_ent)
.expect("could not find client"); .expect("could not find client");
assert_eq!(inv_state.held_item_slot(), 40); assert_eq!(inv_state.held_item_slot(), 40);
} }
@ -525,10 +515,9 @@ fn should_not_increment_state_id_on_cursor_item_change() {
} }
mod dropping_items { mod dropping_items {
use valence_client::packet::{PlayerAction, PlayerActionC2s};
use valence_core::block_pos::BlockPos; use valence_core::block_pos::BlockPos;
use valence_core::direction::Direction; use valence_core::direction::Direction;
use valence_core::packet::c2s::play::click_slot::{ClickMode, Slot};
use valence_core::packet::c2s::play::player_action::Action;
use valence_inventory::convert_to_player_slot_id; use valence_inventory::convert_to_player_slot_id;
use super::*; use super::*;
@ -548,8 +537,8 @@ mod dropping_items {
.expect("could not find inventory"); .expect("could not find inventory");
inventory.set_slot(36, ItemStack::new(ItemKind::IronIngot, 3, None)); inventory.set_slot(36, ItemStack::new(ItemKind::IronIngot, 3, None));
client_helper.send(&valence_core::packet::c2s::play::PlayerActionC2s { client_helper.send(&PlayerActionC2s {
action: Action::DropItem, action: PlayerAction::DropItem,
position: BlockPos::new(0, 0, 0), position: BlockPos::new(0, 0, 0),
direction: Direction::Down, direction: Direction::Down,
sequence: VarInt(0), sequence: VarInt(0),
@ -562,15 +551,19 @@ mod dropping_items {
.world .world
.get::<Inventory>(client_ent) .get::<Inventory>(client_ent)
.expect("could not find client"); .expect("could not find client");
assert_eq!( assert_eq!(
inventory.slot(36), inventory.slot(36),
Some(&ItemStack::new(ItemKind::IronIngot, 2, None)) Some(&ItemStack::new(ItemKind::IronIngot, 2, None))
); );
let events = app let events = app
.world .world
.get_resource::<Events<DropItemStack>>() .get_resource::<Events<DropItemStack>>()
.expect("expected drop item stack events"); .expect("expected drop item stack events");
let events = events.iter_current_update_events().collect::<Vec<_>>(); let events = events.iter_current_update_events().collect::<Vec<_>>();
assert_eq!(events.len(), 1); assert_eq!(events.len(), 1);
assert_eq!(events[0].client, client_ent); assert_eq!(events[0].client, client_ent);
assert_eq!(events[0].from_slot, Some(36)); assert_eq!(events[0].from_slot, Some(36));
@ -580,11 +573,8 @@ mod dropping_items {
); );
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
assert_packet_count!(
sent_packets, sent_packets.assert_count::<ScreenHandlerSlotUpdateS2c>(0);
0,
S2cPlayPacket::ScreenHandlerSlotUpdateS2c(_)
);
} }
#[test] #[test]
@ -602,8 +592,8 @@ mod dropping_items {
.expect("could not find inventory"); .expect("could not find inventory");
inventory.set_slot(36, ItemStack::new(ItemKind::IronIngot, 32, None)); inventory.set_slot(36, ItemStack::new(ItemKind::IronIngot, 32, None));
client_helper.send(&valence_core::packet::c2s::play::PlayerActionC2s { client_helper.send(&PlayerActionC2s {
action: Action::DropAllItems, action: PlayerAction::DropAllItems,
position: BlockPos::new(0, 0, 0), position: BlockPos::new(0, 0, 0),
direction: Direction::Down, direction: Direction::Down,
sequence: VarInt(0), sequence: VarInt(0),
@ -647,12 +637,10 @@ mod dropping_items {
app.world.entity_mut(client_ent).insert(GameMode::Creative); app.world.entity_mut(client_ent).insert(GameMode::Creative);
client_helper.send( client_helper.send(&CreativeInventoryActionC2s {
&valence_core::packet::c2s::play::CreativeInventoryActionC2s {
slot: -1, slot: -1,
clicked_item: Some(ItemStack::new(ItemKind::IronIngot, 32, None)), clicked_item: Some(ItemStack::new(ItemKind::IronIngot, 32, None)),
}, });
);
app.update(); app.update();
@ -693,12 +681,12 @@ mod dropping_items {
.expect("could not find client"); .expect("could not find client");
let state_id = inv_state.state_id().0; let state_id = inv_state.state_id().0;
client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { client_helper.send(&ClickSlotC2s {
window_id: 0, window_id: 0,
state_id: VarInt(state_id),
slot_idx: -999, slot_idx: -999,
button: 0, button: 0,
mode: ClickMode::Click, mode: ClickMode::Click,
state_id: VarInt(state_id),
slot_changes: vec![], slot_changes: vec![],
carried_item: None, carried_item: None,
}); });
@ -710,12 +698,16 @@ mod dropping_items {
.world .world
.get::<CursorItem>(client_ent) .get::<CursorItem>(client_ent)
.expect("could not find client"); .expect("could not find client");
assert_eq!(cursor_item.0, None); assert_eq!(cursor_item.0, None);
let events = app let events = app
.world .world
.get_resource::<Events<DropItemStack>>() .get_resource::<Events<DropItemStack>>()
.expect("expected drop item stack events"); .expect("expected drop item stack events");
let events = events.iter_current_update_events().collect::<Vec<_>>(); let events = events.iter_current_update_events().collect::<Vec<_>>();
assert_eq!(events.len(), 1); assert_eq!(events.len(), 1);
assert_eq!(events[0].client, client_ent); assert_eq!(events[0].client, client_ent);
assert_eq!(events[0].from_slot, None); assert_eq!(events[0].from_slot, None);
@ -738,20 +730,23 @@ mod dropping_items {
.world .world
.get_mut::<ClientInventoryState>(client_ent) .get_mut::<ClientInventoryState>(client_ent)
.expect("could not find client"); .expect("could not find client");
let state_id = inv_state.state_id().0; let state_id = inv_state.state_id().0;
let mut inventory = app let mut inventory = app
.world .world
.get_mut::<Inventory>(client_ent) .get_mut::<Inventory>(client_ent)
.expect("could not find inventory"); .expect("could not find inventory");
inventory.set_slot(40, ItemStack::new(ItemKind::IronIngot, 32, None)); inventory.set_slot(40, ItemStack::new(ItemKind::IronIngot, 32, None));
client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { client_helper.send(&ClickSlotC2s {
window_id: 0, window_id: 0,
slot_idx: 40, slot_idx: 40,
button: 0, button: 0,
mode: ClickMode::DropKey, mode: ClickMode::DropKey,
state_id: VarInt(state_id), state_id: VarInt(state_id),
slot_changes: vec![Slot { slot_changes: vec![SlotChange {
idx: 40, idx: 40,
item: Some(ItemStack::new(ItemKind::IronIngot, 31, None)), item: Some(ItemStack::new(ItemKind::IronIngot, 31, None)),
}], }],
@ -765,7 +760,9 @@ mod dropping_items {
.world .world
.get_resource::<Events<DropItemStack>>() .get_resource::<Events<DropItemStack>>()
.expect("expected drop item stack events"); .expect("expected drop item stack events");
let events = events.iter_current_update_events().collect::<Vec<_>>(); let events = events.iter_current_update_events().collect::<Vec<_>>();
assert_eq!(events.len(), 1); assert_eq!(events.len(), 1);
assert_eq!(events[0].client, client_ent); assert_eq!(events[0].client, client_ent);
assert_eq!(events[0].from_slot, Some(40)); assert_eq!(events[0].from_slot, Some(40));
@ -788,20 +785,23 @@ mod dropping_items {
.world .world
.get_mut::<ClientInventoryState>(client_ent) .get_mut::<ClientInventoryState>(client_ent)
.expect("could not find client"); .expect("could not find client");
let state_id = inv_state.state_id().0; let state_id = inv_state.state_id().0;
let mut inventory = app let mut inventory = app
.world .world
.get_mut::<Inventory>(client_ent) .get_mut::<Inventory>(client_ent)
.expect("could not find inventory"); .expect("could not find inventory");
inventory.set_slot(40, ItemStack::new(ItemKind::IronIngot, 32, None)); inventory.set_slot(40, ItemStack::new(ItemKind::IronIngot, 32, None));
client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { client_helper.send(&ClickSlotC2s {
window_id: 0, window_id: 0,
slot_idx: 40, slot_idx: 40,
button: 1, // pressing control button: 1, // pressing control
mode: ClickMode::DropKey, mode: ClickMode::DropKey,
state_id: VarInt(state_id), state_id: VarInt(state_id),
slot_changes: vec![Slot { slot_changes: vec![SlotChange {
idx: 40, idx: 40,
item: None, item: None,
}], }],
@ -815,7 +815,9 @@ mod dropping_items {
.world .world
.get_resource::<Events<DropItemStack>>() .get_resource::<Events<DropItemStack>>()
.expect("expected drop item stack events"); .expect("expected drop item stack events");
let events = events.iter_current_update_events().collect::<Vec<_>>(); let events = events.iter_current_update_events().collect::<Vec<_>>();
assert_eq!(events.len(), 1); assert_eq!(events.len(), 1);
assert_eq!(events[0].client, client_ent); assert_eq!(events[0].client, client_ent);
assert_eq!(events[0].from_slot, Some(40)); assert_eq!(events[0].from_slot, Some(40));
@ -840,13 +842,16 @@ mod dropping_items {
.world .world
.get_mut::<Inventory>(client_ent) .get_mut::<Inventory>(client_ent)
.expect("could not find inventory"); .expect("could not find inventory");
inventory.set_slot( inventory.set_slot(
convert_to_player_slot_id(InventoryKind::Generic9x3, 50), convert_to_player_slot_id(InventoryKind::Generic9x3, 50),
ItemStack::new(ItemKind::IronIngot, 32, None), ItemStack::new(ItemKind::IronIngot, 32, None),
); );
let _inventory_ent = set_up_open_inventory(&mut app, client_ent); let _inventory_ent = set_up_open_inventory(&mut app, client_ent);
app.update(); app.update();
client_helper.clear_sent(); client_helper.clear_sent();
let inv_state = app let inv_state = app
@ -857,13 +862,13 @@ mod dropping_items {
let state_id = inv_state.state_id().0; let state_id = inv_state.state_id().0;
let window_id = inv_state.window_id(); let window_id = inv_state.window_id();
client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { client_helper.send(&ClickSlotC2s {
window_id, window_id,
slot_idx: 50,
button: 0, // not pressing control
mode: ClickMode::DropKey,
state_id: VarInt(state_id), state_id: VarInt(state_id),
slot_changes: vec![Slot { slot_idx: 50, // not pressing control
button: 0,
mode: ClickMode::DropKey,
slot_changes: vec![SlotChange {
idx: 50, idx: 50,
item: Some(ItemStack::new(ItemKind::IronIngot, 31, None)), item: Some(ItemStack::new(ItemKind::IronIngot, 31, None)),
}], }],
@ -877,21 +882,26 @@ mod dropping_items {
.world .world
.get_resource::<Events<DropItemStack>>() .get_resource::<Events<DropItemStack>>()
.expect("expected drop item stack events"); .expect("expected drop item stack events");
let player_inventory = app let player_inventory = app
.world .world
.get::<Inventory>(client_ent) .get::<Inventory>(client_ent)
.expect("could not find inventory"); .expect("could not find inventory");
let events = events.iter_current_update_events().collect::<Vec<_>>(); let events = events.iter_current_update_events().collect::<Vec<_>>();
assert_eq!(events.len(), 1); assert_eq!(events.len(), 1);
assert_eq!(events[0].client, client_ent); assert_eq!(events[0].client, client_ent);
assert_eq!( assert_eq!(
events[0].from_slot, events[0].from_slot,
Some(convert_to_player_slot_id(InventoryKind::Generic9x3, 50)) Some(convert_to_player_slot_id(InventoryKind::Generic9x3, 50))
); );
assert_eq!( assert_eq!(
events[0].stack, events[0].stack,
ItemStack::new(ItemKind::IronIngot, 1, None) ItemStack::new(ItemKind::IronIngot, 1, None)
); );
// Also make sure that the player inventory was updated correctly. // Also make sure that the player inventory was updated correctly.
let expected_player_slot_id = convert_to_player_slot_id(InventoryKind::Generic9x3, 50); let expected_player_slot_id = convert_to_player_slot_id(InventoryKind::Generic9x3, 50);
assert_eq!( assert_eq!(
@ -916,10 +926,12 @@ fn should_drop_item_stack_player_open_inventory_with_dropkey() {
.world .world
.get_mut::<Inventory>(client_ent) .get_mut::<Inventory>(client_ent)
.expect("could not find inventory"); .expect("could not find inventory");
inventory.set_slot( inventory.set_slot(
convert_to_player_slot_id(InventoryKind::Generic9x3, 50), convert_to_player_slot_id(InventoryKind::Generic9x3, 50),
ItemStack::new(ItemKind::IronIngot, 32, None), ItemStack::new(ItemKind::IronIngot, 32, None),
); );
let _inventory_ent = set_up_open_inventory(&mut app, client_ent); let _inventory_ent = set_up_open_inventory(&mut app, client_ent);
app.update(); app.update();
@ -933,13 +945,13 @@ fn should_drop_item_stack_player_open_inventory_with_dropkey() {
let state_id = inv_state.state_id().0; let state_id = inv_state.state_id().0;
let window_id = inv_state.window_id(); let window_id = inv_state.window_id();
client_helper.send(&valence_core::packet::c2s::play::ClickSlotC2s { client_helper.send(&ClickSlotC2s {
window_id, window_id,
slot_idx: 50,
button: 1, // pressing control, the whole stack is dropped
mode: ClickMode::DropKey,
state_id: VarInt(state_id), state_id: VarInt(state_id),
slot_changes: vec![Slot { slot_idx: 50, // pressing control, the whole stack is dropped
button: 1,
mode: ClickMode::DropKey,
slot_changes: vec![SlotChange {
idx: 50, idx: 50,
item: None, item: None,
}], }],
@ -953,11 +965,14 @@ fn should_drop_item_stack_player_open_inventory_with_dropkey() {
.world .world
.get_resource::<Events<DropItemStack>>() .get_resource::<Events<DropItemStack>>()
.expect("expected drop item stack events"); .expect("expected drop item stack events");
let player_inventory = app let player_inventory = app
.world .world
.get::<Inventory>(client_ent) .get::<Inventory>(client_ent)
.expect("could not find inventory"); .expect("could not find inventory");
let events = events.iter_current_update_events().collect::<Vec<_>>(); let events = events.iter_current_update_events().collect::<Vec<_>>();
assert_eq!(events.len(), 1); assert_eq!(events.len(), 1);
assert_eq!(events[0].client, client_ent); assert_eq!(events[0].client, client_ent);
assert_eq!( assert_eq!(
@ -968,6 +983,7 @@ fn should_drop_item_stack_player_open_inventory_with_dropkey() {
events[0].stack, events[0].stack,
ItemStack::new(ItemKind::IronIngot, 32, None) ItemStack::new(ItemKind::IronIngot, 32, None)
); );
// Also make sure that the player inventory was updated correctly. // Also make sure that the player inventory was updated correctly.
let expected_player_slot_id = convert_to_player_slot_id(InventoryKind::Generic9x3, 50); let expected_player_slot_id = convert_to_player_slot_id(InventoryKind::Generic9x3, 50);
assert_eq!(player_inventory.slot(expected_player_slot_id), None); assert_eq!(player_inventory.slot(expected_player_slot_id), None);
@ -996,15 +1012,15 @@ fn dragging_items() {
button: 2, button: 2,
mode: ClickMode::Drag, mode: ClickMode::Drag,
slot_changes: vec![ slot_changes: vec![
Slot { SlotChange {
idx: 9, idx: 9,
item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), item: Some(ItemStack::new(ItemKind::Diamond, 21, None)),
}, },
Slot { SlotChange {
idx: 10, idx: 10,
item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), item: Some(ItemStack::new(ItemKind::Diamond, 21, None)),
}, },
Slot { SlotChange {
idx: 11, idx: 11,
item: Some(ItemStack::new(ItemKind::Diamond, 21, None)), item: Some(ItemStack::new(ItemKind::Diamond, 21, None)),
}, },
@ -1015,20 +1031,23 @@ fn dragging_items() {
app.update(); app.update();
let sent_packets = client_helper.collect_sent(); let sent_packets = client_helper.collect_sent();
assert_eq!(sent_packets.len(), 0); assert_eq!(sent_packets.0.len(), 0);
let cursor_item = app let cursor_item = app
.world .world
.get::<CursorItem>(client_ent) .get::<CursorItem>(client_ent)
.expect("could not find client"); .expect("could not find client");
assert_eq!( assert_eq!(
cursor_item.0, cursor_item.0,
Some(ItemStack::new(ItemKind::Diamond, 1, None)) Some(ItemStack::new(ItemKind::Diamond, 1, None))
); );
let inventory = app let inventory = app
.world .world
.get::<Inventory>(client_ent) .get::<Inventory>(client_ent)
.expect("could not find inventory"); .expect("could not find inventory");
for i in 9..12 { for i in 9..12 {
assert_eq!( assert_eq!(
inventory.slot(i), inventory.slot(i),

View file

@ -1,51 +1,10 @@
use bevy_app::App; use bevy_app::App;
use valence_client::packet::GameStateChangeS2c;
use valence_client::weather::{Rain, Thunder}; use valence_client::weather::{Rain, Thunder};
use valence_client::Client; use valence_client::Client;
use valence_core::packet::s2c::play::game_state_change::GameEventKind;
use valence_core::packet::s2c::play::{GameStateChangeS2c, S2cPlayPacket};
use super::*; use super::*;
fn assert_weather_packets(sent_packets: Vec<S2cPlayPacket>) {
assert_packet_count!(sent_packets, 6, S2cPlayPacket::GameStateChangeS2c(_));
assert_packet_order!(
sent_packets,
S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c {
kind: GameEventKind::BeginRaining,
value: _
}),
S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c {
kind: GameEventKind::RainLevelChange,
value: _
}),
S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c {
kind: GameEventKind::ThunderLevelChange,
value: _
}),
S2cPlayPacket::GameStateChangeS2c(GameStateChangeS2c {
kind: GameEventKind::EndRaining,
value: _
})
);
if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[1] {
assert_eq!(pkt.value, 0.5);
}
if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[2] {
assert_eq!(pkt.value, 1.0);
}
if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[3] {
assert_eq!(pkt.value, 0.5);
}
if let S2cPlayPacket::GameStateChangeS2c(pkt) = sent_packets[4] {
assert_eq!(pkt.value, 1.0);
}
}
#[test] #[test]
fn test_weather_instance() { fn test_weather_instance() {
let mut app = App::new(); let mut app = App::new();
@ -139,3 +98,8 @@ fn test_weather_client() {
assert_weather_packets(sent_packets); assert_weather_packets(sent_packets);
} }
#[track_caller]
fn assert_weather_packets(sent_packets: PacketFrames) {
sent_packets.assert_count::<GameStateChangeS2c>(6);
}

View file

@ -1,7 +1,8 @@
use bevy_ecs::prelude::{Entity, EventReader, EventWriter}; use bevy_ecs::prelude::{Entity, EventReader, EventWriter};
use valence_client::event_loop::PacketEvent; use valence_client::event_loop::PacketEvent;
use valence_core::ident::Ident; use valence_core::ident::Ident;
use valence_core::packet::c2s::play::AdvancementTabC2s;
use crate::packet::AdvancementTabC2s;
/// This event sends when the client changes or closes advancement's tab. /// This event sends when the client changes or closes advancement's tab.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]

View file

@ -1,11 +1,13 @@
#[doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![allow(clippy::type_complexity)]
pub mod event; pub mod event;
pub mod packet;
use std::borrow::Cow; use std::borrow::Cow;
use std::io::Write; use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::Context;
use bevy_app::{CoreSet, Plugin}; use bevy_app::{CoreSet, Plugin};
use bevy_ecs::prelude::{Bundle, Component, Entity}; use bevy_ecs::prelude::{Bundle, Component, Entity};
use bevy_ecs::query::{Added, Changed, Or, With}; use bevy_ecs::query::{Added, Changed, Or, With};
@ -14,18 +16,15 @@ use bevy_ecs::system::{Commands, Query, SystemParam};
pub use bevy_hierarchy; pub use bevy_hierarchy;
use bevy_hierarchy::{Children, Parent}; use bevy_hierarchy::{Children, Parent};
use event::{handle_advancement_tab_change, AdvancementTabChange}; use event::{handle_advancement_tab_change, AdvancementTabChange};
use packet::SelectAdvancementTabS2c;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use valence_client::{Client, FlushPacketsSet, SpawnClientsSet}; use valence_client::{Client, FlushPacketsSet, SpawnClientsSet};
use valence_core::ident::Ident; use valence_core::ident::Ident;
use valence_core::item::ItemStack; use valence_core::item::ItemStack;
use valence_core::packet::encode::WritePacket; use valence_core::protocol::encode::WritePacket;
use valence_core::packet::raw::RawBytes; use valence_core::protocol::raw::RawBytes;
use valence_core::packet::s2c::play::advancement_update::GenericAdvancementUpdateS2c; use valence_core::protocol::var_int::VarInt;
use valence_core::packet::s2c::play::{ use valence_core::protocol::{packet_id, Encode, Packet};
advancement_update as protocol, AdvancementUpdateS2c, SelectAdvancementTabS2c,
};
use valence_core::packet::var_int::VarInt;
use valence_core::packet::{Encode, Packet};
use valence_core::text::Text; use valence_core::text::Text;
pub struct AdvancementPlugin; pub struct AdvancementPlugin;
@ -81,7 +80,6 @@ fn add_advancement_update_component_to_new_clients(
} }
#[derive(SystemParam, Debug)] #[derive(SystemParam, Debug)]
#[allow(clippy::type_complexity)]
struct UpdateAdvancementCachedBytesQuery<'w, 's> { struct UpdateAdvancementCachedBytesQuery<'w, 's> {
advancement_id_query: Query<'w, 's, &'static Advancement>, advancement_id_query: Query<'w, 's, &'static Advancement>,
criteria_query: Query<'w, 's, &'static AdvancementCriteria>, criteria_query: Query<'w, 's, &'static AdvancementCriteria>,
@ -102,7 +100,7 @@ impl<'w, 's> UpdateAdvancementCachedBytesQuery<'w, 's> {
criteria_query, criteria_query,
} = self; } = self;
let mut pkt = protocol::Advancement { let mut pkt = packet::Advancement {
parent_id: None, parent_id: None,
display_data: None, display_data: None,
criteria: vec![], criteria: vec![],
@ -115,7 +113,7 @@ impl<'w, 's> UpdateAdvancementCachedBytesQuery<'w, 's> {
} }
if let Some(a_display) = a_display { if let Some(a_display) = a_display {
pkt.display_data = Some(protocol::AdvancementDisplay { pkt.display_data = Some(packet::AdvancementDisplay {
title: Cow::Borrowed(&a_display.title), title: Cow::Borrowed(&a_display.title),
description: Cow::Borrowed(&a_display.description), description: Cow::Borrowed(&a_display.description),
icon: &a_display.icon, icon: &a_display.icon,
@ -140,7 +138,7 @@ impl<'w, 's> UpdateAdvancementCachedBytesQuery<'w, 's> {
let c_identifier = criteria_query.get(*requirement)?; let c_identifier = criteria_query.get(*requirement)?;
requirements_p.push(c_identifier.0.as_str()); requirements_p.push(c_identifier.0.as_str());
} }
pkt.requirements.push(protocol::AdvancementRequirements { pkt.requirements.push(packet::AdvancementRequirements {
requirement: requirements_p, requirement: requirements_p,
}); });
} }
@ -149,7 +147,6 @@ impl<'w, 's> UpdateAdvancementCachedBytesQuery<'w, 's> {
} }
} }
#[allow(clippy::type_complexity)]
fn update_advancement_cached_bytes( fn update_advancement_cached_bytes(
mut query: Query< mut query: Query<
( (
@ -218,7 +215,7 @@ impl<'w, 's, 'a> Encode for AdvancementUpdateEncodeS2c<'w, 's, 'a> {
reset, reset,
} = &self.client_update; } = &self.client_update;
let mut pkt = GenericAdvancementUpdateS2c { let mut pkt = packet::GenericAdvancementUpdateS2c {
reset: *reset, reset: *reset,
advancement_mapping: vec![], advancement_mapping: vec![],
identifiers: vec![], identifiers: vec![],
@ -251,7 +248,7 @@ impl<'w, 's, 'a> Encode for AdvancementUpdateEncodeS2c<'w, 's, 'a> {
let mut c_progresses_p = vec![]; let mut c_progresses_p = vec![];
for (c, c_progress) in c_progresses { for (c, c_progress) in c_progresses {
let c_identifier = criteria_query.get(c)?; let c_identifier = criteria_query.get(c)?;
c_progresses_p.push(protocol::AdvancementCriteria { c_progresses_p.push(packet::AdvancementCriteria {
criterion_identifier: c_identifier.0.borrowed(), criterion_identifier: c_identifier.0.borrowed(),
criterion_progress: c_progress, criterion_progress: c_progress,
}); });
@ -264,27 +261,9 @@ impl<'w, 's, 'a> Encode for AdvancementUpdateEncodeS2c<'w, 's, 'a> {
} }
} }
impl<'w, 's, 'a, 'b> Packet<'b> for AdvancementUpdateEncodeS2c<'w, 's, 'a> { impl<'w, 's, 'a> Packet for AdvancementUpdateEncodeS2c<'w, 's, 'a> {
const PACKET_ID: i32 = AdvancementUpdateS2c::PACKET_ID; const ID: i32 = packet_id::ADVANCEMENT_UPDATE_S2C;
const NAME: &'static str = "AdvancementUpdateEncodeS2c";
fn packet_id(&self) -> i32 {
Self::PACKET_ID
}
fn packet_name(&self) -> &str {
"AdvancementUpdateEncodeS2c"
}
fn encode_packet(&self, mut w: impl Write) -> anyhow::Result<()> {
VarInt(Self::PACKET_ID)
.encode(&mut w)
.context("failed to encode packet ID")?;
self.encode(w)
}
fn decode_packet(_r: &mut &'b [u8]) -> anyhow::Result<Self> {
panic!("Packet can not be decoded")
}
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]

View file

@ -1,16 +1,17 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::io::Write; use std::io::Write;
use crate::ident::Ident; use valence_core::ident::Ident;
use crate::item::ItemStack; use valence_core::item::ItemStack;
use crate::packet::var_int::VarInt; use valence_core::protocol::var_int::VarInt;
use crate::packet::{Decode, Encode}; use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use crate::text::Text; use valence_core::text::Text;
pub type AdvancementUpdateS2c<'a> = pub type AdvancementUpdateS2c<'a> =
GenericAdvancementUpdateS2c<'a, (Ident<Cow<'a, str>>, Advancement<'a, Option<ItemStack>>)>; GenericAdvancementUpdateS2c<'a, (Ident<Cow<'a, str>>, Advancement<'a, Option<ItemStack>>)>;
#[derive(Clone, Debug, Encode, Decode)] #[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::ADVANCEMENT_UPDATE_S2C)]
pub struct GenericAdvancementUpdateS2c<'a, AM: 'a> { pub struct GenericAdvancementUpdateS2c<'a, AM: 'a> {
pub reset: bool, pub reset: bool,
pub advancement_mapping: Vec<AM>, pub advancement_mapping: Vec<AM>,
@ -100,3 +101,16 @@ impl<'a, I: Decode<'a>> Decode<'a> for AdvancementDisplay<'a, I> {
}) })
} }
} }
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::ADVANCEMENT_TAB_C2S)]
pub enum AdvancementTabC2s<'a> {
OpenedTab { tab_id: Ident<Cow<'a, str>> },
ClosedScreen,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::SELECT_ADVANCEMENT_TAB_S2C)]
pub struct SelectAdvancementTabS2c<'a> {
pub identifier: Option<Ident<Cow<'a, str>>>,
}

View file

@ -27,8 +27,8 @@ use anyhow::Context;
use valence_core::ident; use valence_core::ident;
use valence_core::ident::Ident; use valence_core::ident::Ident;
use valence_core::item::ItemKind; use valence_core::item::ItemKind;
use valence_core::packet::var_int::VarInt; use valence_core::protocol::var_int::VarInt;
use valence_core::packet::{Decode, Encode}; use valence_core::protocol::{Decode, Encode};
include!(concat!(env!("OUT_DIR"), "/block.rs")); include!(concat!(env!("OUT_DIR"), "/block.rs"));

View file

@ -7,6 +7,7 @@ edition.workspace = true
anyhow.workspace = true anyhow.workspace = true
bevy_app.workspace = true bevy_app.workspace = true
bevy_ecs.workspace = true bevy_ecs.workspace = true
bitfield-struct.workspace = true
bytes.workspace = true bytes.workspace = true
glam.workspace = true glam.workspace = true
rand.workspace = true rand.workspace = true
@ -17,5 +18,5 @@ valence_core.workspace = true
valence_dimension.workspace = true valence_dimension.workspace = true
valence_entity.workspace = true valence_entity.workspace = true
valence_instance.workspace = true valence_instance.workspace = true
valence_nbt.workspace = true
valence_registry.workspace = true valence_registry.workspace = true

View file

@ -1,13 +1,14 @@
use valence_core::block_pos::BlockPos; use valence_core::block_pos::BlockPos;
use valence_core::direction::Direction; use valence_core::direction::Direction;
use valence_core::packet::c2s::play::player_action::Action; use valence_core::protocol::var_int::VarInt;
use valence_core::packet::c2s::play::PlayerActionC2s; use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use super::*; use super::*;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
use crate::packet::{PlayerAction, PlayerActionC2s};
pub(super) fn build(app: &mut App) { pub(super) fn build(app: &mut App) {
app.add_event::<Digging>() app.add_event::<DiggingEvent>()
.add_system( .add_system(
handle_player_action handle_player_action
.in_schedule(EventLoopSchedule) .in_schedule(EventLoopSchedule)
@ -17,7 +18,7 @@ pub(super) fn build(app: &mut App) {
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct Digging { pub struct DiggingEvent {
pub client: Entity, pub client: Entity,
pub position: BlockPos, pub position: BlockPos,
pub direction: Direction, pub direction: Direction,
@ -47,7 +48,7 @@ impl ActionSequence {
fn handle_player_action( fn handle_player_action(
mut clients: Query<&mut ActionSequence>, mut clients: Query<&mut ActionSequence>,
mut packets: EventReader<PacketEvent>, mut packets: EventReader<PacketEvent>,
mut digging_events: EventWriter<Digging>, mut digging_events: EventWriter<DiggingEvent>,
) { ) {
for packet in packets.iter() { for packet in packets.iter() {
if let Some(pkt) = packet.decode::<PlayerActionC2s>() { if let Some(pkt) = packet.decode::<PlayerActionC2s>() {
@ -59,28 +60,28 @@ fn handle_player_action(
// TODO: check that blocks are being broken at the appropriate speeds. // TODO: check that blocks are being broken at the appropriate speeds.
match pkt.action { match pkt.action {
Action::StartDestroyBlock => digging_events.send(Digging { PlayerAction::StartDestroyBlock => digging_events.send(DiggingEvent {
client: packet.client, client: packet.client,
position: pkt.position, position: pkt.position,
direction: pkt.direction, direction: pkt.direction,
state: DiggingState::Start, state: DiggingState::Start,
}), }),
Action::AbortDestroyBlock => digging_events.send(Digging { PlayerAction::AbortDestroyBlock => digging_events.send(DiggingEvent {
client: packet.client, client: packet.client,
position: pkt.position, position: pkt.position,
direction: pkt.direction, direction: pkt.direction,
state: DiggingState::Abort, state: DiggingState::Abort,
}), }),
Action::StopDestroyBlock => digging_events.send(Digging { PlayerAction::StopDestroyBlock => digging_events.send(DiggingEvent {
client: packet.client, client: packet.client,
position: pkt.position, position: pkt.position,
direction: pkt.direction, direction: pkt.direction,
state: DiggingState::Stop, state: DiggingState::Stop,
}), }),
Action::DropAllItems => {} PlayerAction::DropAllItems => {}
Action::DropItem => {} PlayerAction::DropItem => {}
Action::ReleaseUseItem => todo!(), // TODO: release use item. PlayerAction::ReleaseUseItem => {}
Action::SwapItemWithOffhand => {} PlayerAction::SwapItemWithOffhand => {}
} }
} }
} }
@ -99,3 +100,9 @@ fn acknowledge_player_actions(
} }
} }
} }
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_ACTION_RESPONSE_S2C)]
pub struct PlayerActionResponseS2c {
pub sequence: VarInt,
}

View file

@ -0,0 +1,51 @@
// TODO: delete this module in favor of valence_chat.
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use valence_core::protocol::encode::WritePacket;
use valence_core::protocol::packet::chat::{ChatMessageC2s, GameMessageS2c};
use valence_core::text::Text;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
use crate::Client;
pub(super) fn build(app: &mut App) {
app.add_event::<ChatMessageEvent>().add_system(
handle_chat_message
.in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate),
);
}
#[derive(Clone, Debug)]
pub struct ChatMessageEvent {
pub client: Entity,
pub message: Box<str>,
pub timestamp: u64,
}
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(&GameMessageS2c {
chat: msg.into().into(),
overlay: false,
});
}
}
pub fn handle_chat_message(
mut packets: EventReader<PacketEvent>,
mut events: EventWriter<ChatMessageEvent>,
) {
for packet in packets.iter() {
if let Some(pkt) = packet.decode::<ChatMessageC2s>() {
events.send(ChatMessageEvent {
client: packet.client,
message: pkt.message.into(),
timestamp: pkt.timestamp,
});
}
}
}

View file

@ -1,7 +1,7 @@
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use valence_core::packet::c2s::play::client_command::Action; use valence_core::protocol::var_int::VarInt;
use valence_core::packet::c2s::play::ClientCommandC2s; use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_entity::entity::Flags; use valence_entity::entity::Flags;
use valence_entity::{entity, Pose}; use valence_entity::{entity, Pose};
@ -74,7 +74,7 @@ fn handle_client_command(
for packet in packets.iter() { for packet in packets.iter() {
if let Some(pkt) = packet.decode::<ClientCommandC2s>() { if let Some(pkt) = packet.decode::<ClientCommandC2s>() {
match pkt.action { match pkt.action {
Action::StartSneaking => { ClientCommand::StartSneaking => {
if let Ok((mut pose, mut flags)) = clients.get_mut(packet.client) { if let Ok((mut pose, mut flags)) = clients.get_mut(packet.client) {
pose.0 = Pose::Sneaking; pose.0 = Pose::Sneaking;
flags.set_sneaking(true); flags.set_sneaking(true);
@ -85,7 +85,7 @@ fn handle_client_command(
state: SneakState::Start, state: SneakState::Start,
}) })
} }
Action::StopSneaking => { ClientCommand::StopSneaking => {
if let Ok((mut pose, mut flags)) = clients.get_mut(packet.client) { if let Ok((mut pose, mut flags)) = clients.get_mut(packet.client) {
pose.0 = Pose::Standing; pose.0 = Pose::Standing;
flags.set_sneaking(false); flags.set_sneaking(false);
@ -96,10 +96,10 @@ fn handle_client_command(
state: SneakState::Stop, state: SneakState::Stop,
}) })
} }
Action::LeaveBed => leave_bed_events.send(LeaveBed { ClientCommand::LeaveBed => leave_bed_events.send(LeaveBed {
client: packet.client, client: packet.client,
}), }),
Action::StartSprinting => { ClientCommand::StartSprinting => {
if let Ok((_, mut flags)) = clients.get_mut(packet.client) { if let Ok((_, mut flags)) = clients.get_mut(packet.client) {
flags.set_sprinting(true); flags.set_sprinting(true);
} }
@ -109,7 +109,7 @@ fn handle_client_command(
state: SprintState::Start, state: SprintState::Start,
}); });
} }
Action::StopSprinting => { ClientCommand::StopSprinting => {
if let Ok((_, mut flags)) = clients.get_mut(packet.client) { if let Ok((_, mut flags)) = clients.get_mut(packet.client) {
flags.set_sprinting(false); flags.set_sprinting(false);
} }
@ -119,18 +119,18 @@ fn handle_client_command(
state: SprintState::Stop, state: SprintState::Stop,
}) })
} }
Action::StartJumpWithHorse => jump_with_horse_events.send(JumpWithHorse { ClientCommand::StartJumpWithHorse => jump_with_horse_events.send(JumpWithHorse {
client: packet.client, client: packet.client,
state: JumpWithHorseState::Start { state: JumpWithHorseState::Start {
power: pkt.jump_boost.0 as u8, power: pkt.jump_boost.0 as u8,
}, },
}), }),
Action::StopJumpWithHorse => jump_with_horse_events.send(JumpWithHorse { ClientCommand::StopJumpWithHorse => jump_with_horse_events.send(JumpWithHorse {
client: packet.client, client: packet.client,
state: JumpWithHorseState::Stop, state: JumpWithHorseState::Stop,
}), }),
Action::OpenHorseInventory => {} // TODO ClientCommand::OpenHorseInventory => {} // TODO
Action::StartFlyingWithElytra => { ClientCommand::StartFlyingWithElytra => {
if let Ok((mut pose, _)) = clients.get_mut(packet.client) { if let Ok((mut pose, _)) = clients.get_mut(packet.client) {
pose.0 = Pose::FallFlying; pose.0 = Pose::FallFlying;
} }
@ -141,3 +141,24 @@ fn handle_client_command(
} }
} }
} }
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::CLIENT_COMMAND_C2S)]
pub struct ClientCommandC2s {
pub entity_id: VarInt,
pub action: ClientCommand,
pub jump_boost: VarInt,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum ClientCommand {
StartSneaking,
StopSneaking,
LeaveBed,
StartSprinting,
StopSprinting,
StartJumpWithHorse,
StopJumpWithHorse,
OpenHorseInventory,
StartFlyingWithElytra,
}

View file

@ -0,0 +1,58 @@
use valence_core::protocol::raw::RawBytes;
use valence_core::protocol::{packet_id, Decode, Encode};
use super::*;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
pub(super) fn build(app: &mut App) {
app.add_event::<CustomPayloadEvent>().add_system(
handle_custom_payload
.in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate),
);
}
#[derive(Clone, Debug)]
pub struct CustomPayloadEvent {
pub client: Entity,
pub channel: Ident<String>,
pub data: Box<[u8]>,
}
impl Client {
pub fn send_custom_payload(&mut self, channel: Ident<&str>, data: &[u8]) {
self.write_packet(&CustomPayloadS2c {
channel: channel.into(),
data: data.into(),
});
}
}
fn handle_custom_payload(
mut packets: EventReader<PacketEvent>,
mut events: EventWriter<CustomPayloadEvent>,
) {
for packet in packets.iter() {
if let Some(pkt) = packet.decode::<CustomPayloadC2s>() {
events.send(CustomPayloadEvent {
client: packet.client,
channel: pkt.channel.into(),
data: pkt.data.0.into(),
})
}
}
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::CUSTOM_PAYLOAD_C2S)]
pub struct CustomPayloadC2s<'a> {
pub channel: Ident<Cow<'a, str>>,
pub data: RawBytes<'a>,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::CUSTOM_PAYLOAD_S2C)]
pub struct CustomPayloadS2c<'a> {
pub channel: Ident<Cow<'a, str>>,
pub data: RawBytes<'a>,
}

View file

@ -6,7 +6,7 @@ use bevy_ecs::schedule::ScheduleLabel;
use bevy_ecs::system::SystemState; use bevy_ecs::system::SystemState;
use bytes::Bytes; use bytes::Bytes;
use tracing::{debug, warn}; use tracing::{debug, warn};
use valence_core::packet::{Decode, Packet}; use valence_core::protocol::{Decode, Packet};
use valence_entity::hitbox::HitboxUpdateSet; use valence_entity::hitbox::HitboxUpdateSet;
use crate::{Client, SpawnClientsSet}; use crate::{Client, SpawnClientsSet};
@ -69,9 +69,9 @@ impl PacketEvent {
#[inline] #[inline]
pub fn decode<'a, P>(&'a self) -> Option<P> pub fn decode<'a, P>(&'a self) -> Option<P>
where where
P: Packet<'a> + Decode<'a>, P: Packet + Decode<'a>,
{ {
if self.id == P::PACKET_ID { if self.id == P::ID {
let mut r = &self.data[..]; let mut r = &self.data[..];
match P::decode(&mut r) { match P::decode(&mut r) {
@ -83,13 +83,13 @@ impl PacketEvent {
warn!( warn!(
"missed {} bytes while decoding packet {} (ID = {})", "missed {} bytes while decoding packet {} (ID = {})",
r.len(), r.len(),
pkt.packet_name(), P::NAME,
P::PACKET_ID P::ID
); );
debug!("complete packet after partial decode: {pkt:?}"); debug!("complete packet after partial decode: {pkt:?}");
} }
Err(e) => { Err(e) => {
warn!("failed to decode packet with ID of {}: {e:#}", P::PACKET_ID); warn!("failed to decode packet with ID of {}: {e:#}", P::ID);
} }
} }
} }
@ -120,7 +120,7 @@ fn run_event_loop(
client: entity, client: entity,
timestamp: pkt.timestamp, timestamp: pkt.timestamp,
id: pkt.id, id: pkt.id,
data: pkt.data, data: pkt.body,
}); });
let remaining = client.connection().len(); let remaining = client.connection().len();
@ -154,7 +154,7 @@ fn run_event_loop(
client: *entity, client: *entity,
timestamp: pkt.timestamp, timestamp: pkt.timestamp,
id: pkt.id, id: pkt.id,
data: pkt.data, data: pkt.body,
}); });
*remaining -= 1; *remaining -= 1;
// Keep looping as long as there are packets to process this tick. // Keep looping as long as there are packets to process this tick.

View file

@ -0,0 +1,49 @@
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use valence_core::hand::Hand;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_entity::{EntityAnimation, EntityAnimations};
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
pub(super) fn build(app: &mut App) {
app.add_event::<HandSwingEvent>().add_system(
handle_hand_swing
.in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate),
);
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct HandSwingEvent {
pub client: Entity,
pub hand: Hand,
}
fn handle_hand_swing(
mut packets: EventReader<PacketEvent>,
mut clients: Query<&mut EntityAnimations>,
mut events: EventWriter<HandSwingEvent>,
) {
for packet in packets.iter() {
if let Some(pkt) = packet.decode::<HandSwingC2s>() {
if let Ok(mut anim) = clients.get_mut(packet.client) {
anim.trigger(match pkt.hand {
Hand::Main => EntityAnimation::SwingMainHand,
Hand::Off => EntityAnimation::SwingOffHand,
});
}
events.send(HandSwingEvent {
client: packet.client,
hand: pkt.hand,
});
}
}
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::HAND_SWING_C2S)]
pub struct HandSwingC2s {
pub hand: Hand,
}

View file

@ -0,0 +1,73 @@
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use glam::Vec3;
use valence_core::block_pos::BlockPos;
use valence_core::direction::Direction;
use valence_core::hand::Hand;
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use crate::action::ActionSequence;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
pub(super) fn build(app: &mut App) {
app.add_event::<InteractBlockEvent>().add_system(
handle_interact_block
.in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate),
);
}
#[derive(Copy, Clone, Debug)]
pub struct InteractBlockEvent {
pub client: Entity,
/// The hand that was used
pub hand: Hand,
/// The location of the block that was interacted with
pub position: BlockPos,
/// The face of the block that was clicked
pub face: Direction,
/// The position inside of the block that was clicked on
pub cursor_pos: Vec3,
/// Whether or not the player's head is inside a block
pub head_inside_block: bool,
/// Sequence number for synchronization
pub sequence: i32,
}
fn handle_interact_block(
mut packets: EventReader<PacketEvent>,
mut clients: Query<&mut ActionSequence>,
mut events: EventWriter<InteractBlockEvent>,
) {
for packet in packets.iter() {
if let Some(pkt) = packet.decode::<PlayerInteractBlockC2s>() {
if let Ok(mut action_seq) = clients.get_mut(packet.client) {
action_seq.update(pkt.sequence.0);
}
// TODO: check that the block interaction is valid.
events.send(InteractBlockEvent {
client: packet.client,
hand: pkt.hand,
position: pkt.position,
face: pkt.face,
cursor_pos: pkt.cursor_pos,
head_inside_block: pkt.head_inside_block,
sequence: pkt.sequence.0,
});
}
}
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_INTERACT_BLOCK_C2S)]
pub struct PlayerInteractBlockC2s {
pub hand: Hand,
pub position: BlockPos,
pub face: Direction,
pub cursor_pos: Vec3,
pub head_inside_block: bool,
pub sequence: VarInt,
}

View file

@ -1,13 +1,15 @@
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use valence_core::packet::c2s::play::player_interact_entity::EntityInteraction; use glam::Vec3;
use valence_core::packet::c2s::play::PlayerInteractEntityC2s; use valence_core::hand::Hand;
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_entity::EntityManager; use valence_entity::EntityManager;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
pub(super) fn build(app: &mut App) { pub(super) fn build(app: &mut App) {
app.add_event::<InteractEntity>().add_system( app.add_event::<InteractEntityEvent>().add_system(
handle_interact_entity handle_interact_entity
.in_schedule(EventLoopSchedule) .in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate), .in_base_set(EventLoopSet::PreUpdate),
@ -15,7 +17,7 @@ pub(super) fn build(app: &mut App) {
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct InteractEntity { pub struct InteractEntityEvent {
pub client: Entity, pub client: Entity,
/// The entity being interacted with. /// The entity being interacted with.
pub entity: Entity, pub entity: Entity,
@ -25,10 +27,17 @@ pub struct InteractEntity {
pub interact: EntityInteraction, pub interact: EntityInteraction,
} }
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)]
pub enum EntityInteraction {
Interact(Hand),
Attack,
InteractAt { target: Vec3, hand: Hand },
}
fn handle_interact_entity( fn handle_interact_entity(
mut packets: EventReader<PacketEvent>, mut packets: EventReader<PacketEvent>,
entities: Res<EntityManager>, entities: Res<EntityManager>,
mut events: EventWriter<InteractEntity>, mut events: EventWriter<InteractEntityEvent>,
) { ) {
for packet in packets.iter() { for packet in packets.iter() {
if let Some(pkt) = packet.decode::<PlayerInteractEntityC2s>() { if let Some(pkt) = packet.decode::<PlayerInteractEntityC2s>() {
@ -37,7 +46,7 @@ fn handle_interact_entity(
// within some configurable tolerance level. // within some configurable tolerance level.
if let Some(entity) = entities.get_by_id(pkt.entity_id.0) { if let Some(entity) = entities.get_by_id(pkt.entity_id.0) {
events.send(InteractEntity { events.send(InteractEntityEvent {
client: packet.client, client: packet.client,
entity, entity,
sneaking: pkt.sneaking, sneaking: pkt.sneaking,
@ -47,3 +56,11 @@ fn handle_interact_entity(
} }
} }
} }
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_INTERACT_ENTITY_C2S)]
pub struct PlayerInteractEntityC2s {
pub entity_id: VarInt,
pub interact: EntityInteraction,
pub sneaking: bool,
}

View file

@ -0,0 +1,50 @@
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use valence_core::hand::Hand;
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use crate::action::ActionSequence;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
pub(super) fn build(app: &mut App) {
app.add_event::<InteractItemEvent>().add_system(
handle_player_interact_item
.in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate),
);
}
#[derive(Copy, Clone, Debug)]
pub struct InteractItemEvent {
pub client: Entity,
pub hand: Hand,
pub sequence: i32,
}
fn handle_player_interact_item(
mut packets: EventReader<PacketEvent>,
mut clients: Query<&mut ActionSequence>,
mut events: EventWriter<InteractItemEvent>,
) {
for packet in packets.iter() {
if let Some(pkt) = packet.decode::<PlayerInteractItemC2s>() {
if let Ok(mut action_seq) = clients.get_mut(packet.client) {
action_seq.update(pkt.sequence.0);
}
events.send(InteractItemEvent {
client: packet.client,
hand: pkt.hand,
sequence: pkt.sequence.0,
});
}
}
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_INTERACT_ITEM_C2S)]
pub struct PlayerInteractItemC2s {
pub hand: Hand,
pub sequence: VarInt,
}

View file

@ -1,4 +1,4 @@
use valence_core::packet::c2s::play::KeepAliveC2s; use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_core::CoreSettings; use valence_core::CoreSettings;
use super::*; use super::*;
@ -81,3 +81,15 @@ fn handle_keepalive_response(
} }
} }
} }
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::KEEP_ALIVE_C2S)]
pub struct KeepAliveC2s {
pub id: u64,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::KEEP_ALIVE_S2C)]
pub struct KeepAliveS2c {
pub id: u64,
}

View file

@ -29,6 +29,10 @@ use bevy_ecs::query::WorldQuery;
use bevy_ecs::system::Command; use bevy_ecs::system::Command;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use glam::{DVec3, Vec3}; use glam::{DVec3, Vec3};
use packet::{
DeathMessageS2c, DisconnectS2c, GameEventKind, GameJoinS2c, GameStateChangeS2c,
PlayerRespawnS2c, PlayerSpawnPositionS2c, PlayerSpawnS2c,
};
use rand::Rng; use rand::Rng;
use tracing::warn; use tracing::warn;
use uuid::Uuid; use uuid::Uuid;
@ -38,45 +42,51 @@ use valence_core::chunk_pos::{ChunkPos, ChunkView};
use valence_core::despawn::Despawned; use valence_core::despawn::Despawned;
use valence_core::game_mode::GameMode; use valence_core::game_mode::GameMode;
use valence_core::ident::Ident; use valence_core::ident::Ident;
use valence_core::packet::byte_angle::ByteAngle; use valence_core::particle::{Particle, ParticleS2c};
use valence_core::packet::encode::{PacketEncoder, WritePacket};
use valence_core::packet::global_pos::GlobalPos;
use valence_core::packet::s2c::play::game_state_change::GameEventKind;
use valence_core::packet::s2c::play::particle::Particle;
use valence_core::packet::s2c::play::player_position_look::Flags as PlayerPositionLookFlags;
use valence_core::packet::s2c::play::{
ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, CustomPayloadS2c, DeathMessageS2c,
DisconnectS2c, EntitiesDestroyS2c, EntitySetHeadYawS2c, EntitySpawnS2c, EntityStatusS2c,
EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, ExperienceOrbSpawnS2c, GameJoinS2c,
GameMessageS2c, GameStateChangeS2c, KeepAliveS2c, OverlayMessageS2c, ParticleS2c, PlaySoundS2c,
PlayerActionResponseS2c, PlayerPositionLookS2c, PlayerRespawnS2c, PlayerSpawnPositionS2c,
PlayerSpawnS2c, ResourcePackSendS2c, SubtitleS2c, TitleFadeS2c, TitleS2c, UnloadChunkS2c,
};
use valence_core::packet::var_int::VarInt;
use valence_core::packet::Packet;
use valence_core::property::Property; use valence_core::property::Property;
use valence_core::protocol::byte_angle::ByteAngle;
use valence_core::protocol::encode::{PacketEncoder, WritePacket};
use valence_core::protocol::global_pos::GlobalPos;
use valence_core::protocol::packet::sound::{PlaySoundS2c, Sound, SoundCategory};
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{Encode, Packet};
use valence_core::scratch::ScratchBuf; use valence_core::scratch::ScratchBuf;
use valence_core::sound::{Sound, SoundCategory};
use valence_core::text::Text; use valence_core::text::Text;
use valence_core::uuid::UniqueId; use valence_core::uuid::UniqueId;
use valence_core::Server; use valence_core::Server;
use valence_entity::packet::{
EntitiesDestroyS2c, EntitySetHeadYawS2c, EntitySpawnS2c, EntityStatusS2c,
EntityTrackerUpdateS2c, EntityVelocityUpdateS2c, ExperienceOrbSpawnS2c,
};
use valence_entity::player::PlayerEntityBundle; use valence_entity::player::PlayerEntityBundle;
use valence_entity::{ use valence_entity::{
ClearEntityChangesSet, EntityId, EntityKind, EntityStatus, HeadYaw, Location, Look, ObjectData, ClearEntityChangesSet, EntityId, EntityKind, EntityStatus, HeadYaw, Location, Look, ObjectData,
OldLocation, OldPosition, OnGround, PacketByteRange, Position, TrackedData, Velocity, OldLocation, OldPosition, OnGround, PacketByteRange, Position, TrackedData, Velocity,
}; };
use valence_instance::packet::{
ChunkLoadDistanceS2c, ChunkRenderDistanceCenterS2c, UnloadChunkS2c,
};
use valence_instance::{ClearInstanceChangesSet, Instance, WriteUpdatePacketsToInstancesSet}; use valence_instance::{ClearInstanceChangesSet, Instance, WriteUpdatePacketsToInstancesSet};
use valence_registry::{RegistryCodec, RegistryCodecSet}; use valence_registry::{RegistryCodec, RegistryCodecSet};
pub mod action; pub mod action;
pub mod chat;
pub mod command; pub mod command;
pub mod custom_payload;
pub mod event_loop; pub mod event_loop;
pub mod hand_swing;
pub mod interact_block;
pub mod interact_entity; pub mod interact_entity;
pub mod interact_item;
pub mod keepalive; pub mod keepalive;
pub mod misc;
pub mod movement; pub mod movement;
pub mod op_level;
pub mod packet;
pub mod resource_pack;
pub mod settings; pub mod settings;
pub mod status;
pub mod teleport; pub mod teleport;
pub mod title;
pub mod weather; pub mod weather;
pub struct ClientPlugin; pub struct ClientPlugin;
@ -115,7 +125,6 @@ impl Plugin for ClientPlugin {
update_game_mode, update_game_mode,
update_tracked_data.after(WriteUpdatePacketsToInstancesSet), update_tracked_data.after(WriteUpdatePacketsToInstancesSet),
init_tracked_data.after(WriteUpdatePacketsToInstancesSet), init_tracked_data.after(WriteUpdatePacketsToInstancesSet),
update_op_level,
) )
.in_set(UpdateClientsSet), .in_set(UpdateClientsSet),
) )
@ -136,10 +145,17 @@ impl Plugin for ClientPlugin {
keepalive::build(app); keepalive::build(app);
interact_entity::build(app); interact_entity::build(app);
settings::build(app); settings::build(app);
misc::build(app);
action::build(app); action::build(app);
teleport::build(app); teleport::build(app);
weather::build(app); weather::build(app);
chat::build(app);
custom_payload::build(app);
hand_swing::build(app);
interact_block::build(app);
interact_item::build(app);
op_level::build(app);
resource_pack::build(app);
status::build(app);
} }
} }
@ -156,7 +172,7 @@ pub struct ClientBundle {
pub properties: Properties, pub properties: Properties,
pub compass_pos: CompassPos, pub compass_pos: CompassPos,
pub game_mode: GameMode, pub game_mode: GameMode,
pub op_level: OpLevel, pub op_level: op_level::OpLevel,
pub action_sequence: action::ActionSequence, pub action_sequence: action::ActionSequence,
pub view_distance: ViewDistance, pub view_distance: ViewDistance,
pub old_view_distance: OldViewDistance, pub old_view_distance: OldViewDistance,
@ -189,7 +205,7 @@ impl ClientBundle {
properties: Properties(args.properties), properties: Properties(args.properties),
compass_pos: CompassPos::default(), compass_pos: CompassPos::default(),
game_mode: GameMode::default(), game_mode: GameMode::default(),
op_level: OpLevel::default(), op_level: op_level::OpLevel::default(),
action_sequence: action::ActionSequence::default(), action_sequence: action::ActionSequence::default(),
view_distance: ViewDistance::default(), view_distance: ViewDistance::default(),
old_view_distance: OldViewDistance(2), old_view_distance: OldViewDistance(2),
@ -261,7 +277,7 @@ pub struct ReceivedPacket {
/// This packet's ID. /// This packet's ID.
pub id: i32, pub id: i32,
/// The content of the packet, excluding the leading varint packet ID. /// The content of the packet, excluding the leading varint packet ID.
pub data: Bytes, pub body: Bytes,
} }
impl Drop for Client { impl Drop for Client {
@ -273,7 +289,10 @@ impl Drop for Client {
/// Writes packets into this client's packet buffer. The buffer is flushed at /// Writes packets into this client's packet buffer. The buffer is flushed at
/// the end of the tick. /// the end of the tick.
impl WritePacket for Client { impl WritePacket for Client {
fn write_packet<'a>(&mut self, packet: &impl Packet<'a>) { fn write_packet<P>(&mut self, packet: &P)
where
P: Packet + Encode,
{
self.enc.write_packet(packet) self.enc.write_packet(packet)
} }
@ -307,22 +326,6 @@ 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(&GameMessageS2c {
chat: msg.into().into(),
overlay: false,
});
}
pub fn send_custom_payload(&mut self, channel: Ident<&str>, data: &[u8]) {
self.write_packet(&CustomPayloadS2c {
channel: channel.into(),
data: data.into(),
});
}
/// Kills the client and shows `message` on the death screen. If an entity /// Kills the client and shows `message` on the death screen. If an entity
/// killed the player, you should supply it as `killer`. /// killed the player, you should supply it as `killer`.
pub fn kill(&mut self, killer: Option<EntityId>, message: impl Into<Text>) { pub fn kill(&mut self, killer: Option<EntityId>, message: impl Into<Text>) {
@ -341,69 +344,6 @@ impl Client {
}); });
} }
/// Requests that the client download and enable a resource pack.
///
/// # Arguments
/// * `url` - The URL of the resource pack file.
/// * `hash` - The SHA-1 hash of the resource pack file. Any value other
/// than a 40-character hexadecimal string is ignored by the client.
/// * `forced` - Whether a client should be kicked from the server upon
/// declining the pack (this is enforced client-side)
/// * `prompt_message` - A message to be displayed with the resource pack
/// dialog.
pub fn set_resource_pack(
&mut self,
url: &str,
hash: &str,
forced: bool,
prompt_message: Option<Text>,
) {
self.write_packet(&ResourcePackSendS2c {
url,
hash,
forced,
prompt_message: prompt_message.map(|t| t.into()),
});
}
/// Sets the title this client sees.
///
/// 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 [`TitleFadeS2c`]
/// struct.
pub fn set_title(
&mut self,
title: impl Into<Text>,
subtitle: impl Into<Text>,
animation: impl Into<Option<TitleFadeS2c>>,
) {
let title = title.into().into();
let subtitle = subtitle.into();
self.write_packet(&TitleS2c { title_text: title });
if !subtitle.is_empty() {
self.write_packet(&SubtitleS2c {
subtitle_text: subtitle.into(),
});
}
if let Some(anim) = animation.into() {
self.write_packet(&anim);
}
}
/// Sets the action bar for this 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(&OverlayMessageS2c {
action_bar_text: text.into().into(),
});
}
/// Puts a particle effect at the given position, only for this client. /// Puts a particle effect at the given position, only for this client.
/// ///
/// If you want to show a particle effect to all players, use /// If you want to show a particle effect to all players, use
@ -593,20 +533,6 @@ pub struct Ip(pub IpAddr);
#[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)] #[derive(Component, Copy, Clone, PartialEq, Eq, Default, Debug)]
pub struct CompassPos(pub BlockPos); pub struct CompassPos(pub BlockPos);
#[derive(Component, Clone, PartialEq, Eq, Default, Debug)]
pub struct OpLevel(u8);
impl OpLevel {
pub fn get(&self) -> u8 {
self.0
}
/// Sets the op level. Value is clamped to `0..=3`.
pub fn set(&mut self, lvl: u8) {
self.0 = lvl.min(3);
}
}
#[derive(Component, Clone, PartialEq, Eq, Debug)] #[derive(Component, Clone, PartialEq, Eq, Debug)]
pub struct ViewDistance(u8); pub struct ViewDistance(u8);
@ -1269,12 +1195,3 @@ fn update_tracked_data(mut clients: Query<(&mut Client, &TrackedData)>) {
} }
} }
} }
fn update_op_level(mut clients: Query<(&mut Client, &OpLevel), Changed<OpLevel>>) {
for (mut client, lvl) in &mut clients {
client.write_packet(&EntityStatusS2c {
entity_id: 0,
entity_status: 24 + lvl.0,
});
}
}

View file

@ -1,159 +0,0 @@
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use glam::Vec3;
use valence_core::block_pos::BlockPos;
use valence_core::direction::Direction;
use valence_core::hand::Hand;
use valence_core::packet::c2s::play::{
ChatMessageC2s, ClientStatusC2s, HandSwingC2s, PlayerInteractBlockC2s, PlayerInteractItemC2s,
ResourcePackStatusC2s,
};
use valence_entity::{EntityAnimation, EntityAnimations};
use super::action::ActionSequence;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
pub(super) fn build(app: &mut App) {
app.add_event::<HandSwing>()
.add_event::<InteractBlock>()
.add_event::<ChatMessage>()
.add_event::<Respawn>()
.add_event::<RequestStats>()
.add_event::<ResourcePackStatusChange>()
.add_system(
handle_misc_packets
.in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate),
);
}
#[derive(Copy, Clone, Debug)]
pub struct HandSwing {
pub client: Entity,
pub hand: Hand,
}
#[derive(Copy, Clone, Debug)]
pub struct InteractBlock {
pub client: Entity,
/// The hand that was used
pub hand: Hand,
/// The location of the block that was interacted with
pub position: BlockPos,
/// The face of the block that was clicked
pub face: Direction,
/// The position inside of the block that was clicked on
pub cursor_pos: Vec3,
/// Whether or not the player's head is inside a block
pub head_inside_block: bool,
/// Sequence number for synchronization
pub sequence: i32,
}
#[derive(Clone, Debug)]
pub struct ChatMessage {
pub client: Entity,
pub message: Box<str>,
pub timestamp: u64,
}
#[derive(Copy, Clone, Debug)]
pub struct Respawn {
pub client: Entity,
}
#[derive(Copy, Clone, Debug)]
pub struct RequestStats {
pub client: Entity,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ResourcePackStatus {
/// The client has accepted the server's resource pack.
Accepted,
/// The client has declined the server's resource pack.
Declined,
/// The client has successfully loaded the server's resource pack.
Loaded,
/// The client has failed to download the server's resource pack.
FailedDownload,
}
#[derive(Clone, Debug)]
pub struct ResourcePackStatusChange {
pub client: Entity,
pub status: ResourcePackStatus,
}
#[allow(clippy::too_many_arguments)]
fn handle_misc_packets(
mut packets: EventReader<PacketEvent>,
mut clients: Query<(&mut ActionSequence, &mut EntityAnimations)>,
mut hand_swing_events: EventWriter<HandSwing>,
mut interact_block_events: EventWriter<InteractBlock>,
mut chat_message_events: EventWriter<ChatMessage>,
mut respawn_events: EventWriter<Respawn>,
mut request_stats_events: EventWriter<RequestStats>,
mut resource_pack_status_change_events: EventWriter<ResourcePackStatusChange>,
) {
for packet in packets.iter() {
if let Some(pkt) = packet.decode::<HandSwingC2s>() {
if let Ok((_, mut animations)) = clients.get_mut(packet.client) {
animations.trigger(match pkt.hand {
Hand::Main => EntityAnimation::SwingMainHand,
Hand::Off => EntityAnimation::SwingOffHand,
});
}
hand_swing_events.send(HandSwing {
client: packet.client,
hand: pkt.hand,
});
} else if let Some(pkt) = packet.decode::<PlayerInteractBlockC2s>() {
if let Ok((mut action_seq, _)) = clients.get_mut(packet.client) {
action_seq.update(pkt.sequence.0);
}
interact_block_events.send(InteractBlock {
client: packet.client,
hand: pkt.hand,
position: pkt.position,
face: pkt.face,
cursor_pos: pkt.cursor_pos,
head_inside_block: pkt.head_inside_block,
sequence: pkt.sequence.0,
});
} else if let Some(pkt) = packet.decode::<PlayerInteractItemC2s>() {
if let Ok((mut action_seq, _)) = clients.get_mut(packet.client) {
action_seq.update(pkt.sequence.0);
}
// TODO
} else if let Some(pkt) = packet.decode::<ChatMessageC2s>() {
chat_message_events.send(ChatMessage {
client: packet.client,
message: pkt.message.into(),
timestamp: pkt.timestamp,
});
} else if let Some(pkt) = packet.decode::<ClientStatusC2s>() {
match pkt {
ClientStatusC2s::PerformRespawn => respawn_events.send(Respawn {
client: packet.client,
}),
ClientStatusC2s::RequestStats => request_stats_events.send(RequestStats {
client: packet.client,
}),
}
} else if let Some(pkt) = packet.decode::<ResourcePackStatusC2s>() {
resource_pack_status_change_events.send(ResourcePackStatusChange {
client: packet.client,
status: match pkt {
ResourcePackStatusC2s::Accepted => ResourcePackStatus::Accepted,
ResourcePackStatusC2s::Declined => ResourcePackStatus::Declined,
ResourcePackStatusC2s::SuccessfullyLoaded => ResourcePackStatus::Loaded,
ResourcePackStatusC2s::FailedDownload => ResourcePackStatus::FailedDownload,
},
});
}
}
}

View file

@ -1,9 +1,7 @@
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use glam::DVec3; use glam::DVec3;
use valence_core::packet::c2s::play::{ use valence_core::protocol::{packet_id, Decode, Encode, Packet};
Full, LookAndOnGround, OnGroundOnly, PositionAndOnGround, VehicleMoveC2s,
};
use valence_entity::{HeadYaw, Look, OnGround, Position}; use valence_entity::{HeadYaw, Look, OnGround, Position};
use super::teleport::TeleportState; use super::teleport::TeleportState;
@ -11,7 +9,7 @@ use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
pub(super) fn build(app: &mut App) { pub(super) fn build(app: &mut App) {
app.init_resource::<MovementSettings>() app.init_resource::<MovementSettings>()
.add_event::<Movement>() .add_event::<MovementEvent>()
.add_system( .add_system(
handle_client_movement handle_client_movement
.in_schedule(EventLoopSchedule) .in_schedule(EventLoopSchedule)
@ -27,7 +25,7 @@ pub struct MovementSettings {
/// Event sent when a client successfully moves. /// Event sent when a client successfully moves.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Movement { pub struct MovementEvent {
pub client: Entity, pub client: Entity,
pub position: DVec3, pub position: DVec3,
pub old_position: DVec3, pub old_position: DVec3,
@ -46,14 +44,14 @@ fn handle_client_movement(
&mut OnGround, &mut OnGround,
&mut TeleportState, &mut TeleportState,
)>, )>,
mut movement_events: EventWriter<Movement>, mut movement_events: EventWriter<MovementEvent>,
) { ) {
for packet in packets.iter() { for packet in packets.iter() {
if let Some(pkt) = packet.decode::<PositionAndOnGround>() { if let Some(pkt) = packet.decode::<PositionAndOnGroundC2s>() {
if let Ok((pos, look, head_yaw, on_ground, teleport_state)) = if let Ok((pos, look, head_yaw, on_ground, teleport_state)) =
clients.get_mut(packet.client) clients.get_mut(packet.client)
{ {
let mov = Movement { let mov = MovementEvent {
client: packet.client, client: packet.client,
position: pkt.position, position: pkt.position,
old_position: pos.0, old_position: pos.0,
@ -73,11 +71,11 @@ fn handle_client_movement(
&mut movement_events, &mut movement_events,
); );
} }
} else if let Some(pkt) = packet.decode::<Full>() { } else if let Some(pkt) = packet.decode::<FullC2s>() {
if let Ok((pos, look, head_yaw, on_ground, teleport_state)) = if let Ok((pos, look, head_yaw, on_ground, teleport_state)) =
clients.get_mut(packet.client) clients.get_mut(packet.client)
{ {
let mov = Movement { let mov = MovementEvent {
client: packet.client, client: packet.client,
position: pkt.position, position: pkt.position,
old_position: pos.0, old_position: pos.0,
@ -100,11 +98,11 @@ fn handle_client_movement(
&mut movement_events, &mut movement_events,
); );
} }
} else if let Some(pkt) = packet.decode::<LookAndOnGround>() { } else if let Some(pkt) = packet.decode::<LookAndOnGroundC2s>() {
if let Ok((pos, look, head_yaw, on_ground, teleport_state)) = if let Ok((pos, look, head_yaw, on_ground, teleport_state)) =
clients.get_mut(packet.client) clients.get_mut(packet.client)
{ {
let mov = Movement { let mov = MovementEvent {
client: packet.client, client: packet.client,
position: pos.0, position: pos.0,
old_position: pos.0, old_position: pos.0,
@ -127,11 +125,11 @@ fn handle_client_movement(
&mut movement_events, &mut movement_events,
); );
} }
} else if let Some(pkt) = packet.decode::<OnGroundOnly>() { } else if let Some(pkt) = packet.decode::<OnGroundOnlyC2s>() {
if let Ok((pos, look, head_yaw, on_ground, teleport_state)) = if let Ok((pos, look, head_yaw, on_ground, teleport_state)) =
clients.get_mut(packet.client) clients.get_mut(packet.client)
{ {
let mov = Movement { let mov = MovementEvent {
client: packet.client, client: packet.client,
position: pos.0, position: pos.0,
old_position: pos.0, old_position: pos.0,
@ -155,7 +153,7 @@ fn handle_client_movement(
if let Ok((pos, look, head_yaw, on_ground, teleport_state)) = if let Ok((pos, look, head_yaw, on_ground, teleport_state)) =
clients.get_mut(packet.client) clients.get_mut(packet.client)
{ {
let mov = Movement { let mov = MovementEvent {
client: packet.client, client: packet.client,
position: pkt.position, position: pkt.position,
old_position: pos.0, old_position: pos.0,
@ -183,13 +181,13 @@ fn handle_client_movement(
} }
fn handle( fn handle(
mov: Movement, mov: MovementEvent,
mut pos: Mut<Position>, mut pos: Mut<Position>,
mut look: Mut<Look>, mut look: Mut<Look>,
mut head_yaw: Mut<HeadYaw>, mut head_yaw: Mut<HeadYaw>,
mut on_ground: Mut<OnGround>, mut on_ground: Mut<OnGround>,
mut teleport_state: Mut<TeleportState>, mut teleport_state: Mut<TeleportState>,
movement_events: &mut EventWriter<Movement>, movement_events: &mut EventWriter<MovementEvent>,
) { ) {
if teleport_state.pending_teleports() != 0 { if teleport_state.pending_teleports() != 0 {
return; return;
@ -207,3 +205,41 @@ fn handle(
movement_events.send(mov); movement_events.send(mov);
} }
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::POSITION_AND_ON_GROUND)]
pub struct PositionAndOnGroundC2s {
pub position: DVec3,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::FULL)]
pub struct FullC2s {
pub position: DVec3,
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::LOOK_AND_ON_GROUND)]
pub struct LookAndOnGroundC2s {
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::ON_GROUND_ONLY)]
pub struct OnGroundOnlyC2s {
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::VEHICLE_MOVE_C2S)]
pub struct VehicleMoveC2s {
pub position: DVec3,
pub yaw: f32,
pub pitch: f32,
}

View file

@ -0,0 +1,30 @@
use valence_entity::packet::EntityStatusS2c;
use super::*;
pub(super) fn build(app: &mut App) {
app.add_system(update_op_level.in_set(UpdateClientsSet));
}
#[derive(Component, Clone, PartialEq, Eq, Default, Debug)]
pub struct OpLevel(u8);
impl OpLevel {
pub fn get(&self) -> u8 {
self.0
}
/// Sets the op level. Value is clamped to `0..=3`.
pub fn set(&mut self, lvl: u8) {
self.0 = lvl.min(3);
}
}
fn update_op_level(mut clients: Query<(&mut Client, &OpLevel), Changed<OpLevel>>) {
for (mut client, lvl) in &mut clients {
client.write_packet(&EntityStatusS2c {
entity_id: 0,
entity_status: 24 + lvl.0,
});
}
}

View file

@ -0,0 +1,468 @@
//! Common packets for this crate.
use std::borrow::Cow;
use bitfield_struct::bitfield;
use glam::DVec3;
use uuid::Uuid;
use valence_core::block_pos::BlockPos;
use valence_core::difficulty::Difficulty;
use valence_core::direction::Direction;
use valence_core::game_mode::GameMode;
use valence_core::hand::Hand;
use valence_core::ident::Ident;
use valence_core::protocol::byte_angle::ByteAngle;
use valence_core::protocol::global_pos::GlobalPos;
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::var_long::VarLong;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_core::text::Text;
use valence_nbt::Compound;
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::BUNDLE_SPLITTER)]
pub struct BundleSplitterS2c;
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::BOAT_PADDLE_STATE_C2S)]
pub struct BoatPaddleStateC2s {
pub left_paddle_turning: bool,
pub right_paddle_turning: bool,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::BOOK_UPDATE_C2S)]
pub struct BookUpdateC2s<'a> {
pub slot: VarInt,
pub entries: Vec<&'a str>,
pub title: Option<&'a str>,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::JIGSAW_GENERATING_C2S)]
pub struct JigsawGeneratingC2s {
pub position: BlockPos,
pub levels: VarInt,
pub keep_jigsaws: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAY_PONG_C2S)]
pub struct PlayPongC2s {
pub id: i32,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_ACTION_C2S)]
pub struct PlayerActionC2s {
pub action: PlayerAction,
pub position: BlockPos,
pub direction: Direction,
pub sequence: VarInt,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum PlayerAction {
StartDestroyBlock,
AbortDestroyBlock,
StopDestroyBlock,
DropAllItems,
DropItem,
ReleaseUseItem,
SwapItemWithOffhand,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_INPUT_C2S)]
pub struct PlayerInputC2s {
pub sideways: f32,
pub forward: f32,
pub flags: PlayerInputFlags,
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
pub struct PlayerInputFlags {
pub jump: bool,
pub unmount: bool,
#[bits(6)]
_pad: u8,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::QUERY_BLOCK_NBT_C2S)]
pub struct QueryBlockNbtC2s {
pub transaction_id: VarInt,
pub position: BlockPos,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::QUERY_ENTITY_NBT_C2S)]
pub struct QueryEntityNbtC2s {
pub transaction_id: VarInt,
pub entity_id: VarInt,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::SPECTATOR_TELEPORT_C2S)]
pub struct SpectatorTeleportC2s {
pub target: Uuid,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::UPDATE_COMMAND_BLOCK_MINECART_C2S)]
pub struct UpdateCommandBlockMinecartC2s<'a> {
pub entity_id: VarInt,
pub command: &'a str,
pub track_output: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::UPDATE_COMMAND_BLOCK_C2S)]
pub struct UpdateCommandBlockC2s<'a> {
pub position: BlockPos,
pub command: &'a str,
pub mode: UpdateCommandBlockMode,
pub flags: UpdateCommandBlockFlags,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum UpdateCommandBlockMode {
Sequence,
Auto,
Redstone,
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
pub struct UpdateCommandBlockFlags {
pub track_output: bool,
pub conditional: bool,
pub automatic: bool,
#[bits(5)]
_pad: u8,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::UPDATE_DIFFICULTY_LOCK_C2S)]
pub struct UpdateDifficultyLockC2s {
pub locked: bool,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::UPDATE_DIFFICULTY_C2S)]
pub struct UpdateDifficultyC2s {
pub difficulty: Difficulty,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::UPDATE_JIGSAW_C2S)]
pub struct UpdateJigsawC2s<'a> {
pub position: BlockPos,
pub name: Ident<Cow<'a, str>>,
pub target: Ident<Cow<'a, str>>,
pub pool: Ident<Cow<'a, str>>,
pub final_state: &'a str,
pub joint_type: &'a str,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::UPDATE_PLAYER_ABILITIES_C2S)]
pub enum UpdatePlayerAbilitiesC2s {
#[packet(tag = 0b00)]
StopFlying,
#[packet(tag = 0b10)]
StartFlying,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::UPDATE_SIGN_C2S)]
pub struct UpdateSignC2s<'a> {
pub position: BlockPos,
pub lines: [&'a str; 4],
}
pub mod structure_block {
use super::*;
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::UPDATE_STRUCTURE_BLOCK_C2S)]
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,
}
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::DEATH_MESSAGE_S2C)]
pub struct DeathMessageS2c<'a> {
pub player_id: VarInt,
/// Killer's entity ID, -1 if no killer
pub entity_id: i32,
pub message: Cow<'a, Text>,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::DAMAGE_TILT_S2C)]
pub struct DamageTiltS2c {
/// The ID of the entity taking damage.
pub entity_id: VarInt,
/// The direction the damage is coming from in relation to the entity.
pub yaw: f32,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::DIFFICULTY_S2C)]
pub struct DifficultyS2c {
pub difficulty: Difficulty,
pub locked: bool,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::DISCONNECT_S2C)]
pub struct DisconnectS2c<'a> {
pub reason: Cow<'a, Text>,
}
/// Unused by notchian clients.
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::ENTER_COMBAT_S2C)]
pub struct EnterCombatS2c;
/// Unused by notchian clients.
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::END_COMBAT_S2C)]
pub struct EndCombatS2c {
pub duration: VarInt,
pub entity_id: i32,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::EXPERIENCE_BAR_UPDATE_S2C)]
pub struct ExperienceBarUpdateS2c {
pub bar: f32,
pub level: VarInt,
pub total_xp: VarInt,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::FEATURES_S2C)]
pub struct FeaturesS2c<'a> {
pub features: Vec<Ident<Cow<'a, str>>>,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::GAME_JOIN_S2C)]
pub struct GameJoinS2c<'a> {
pub entity_id: i32,
pub is_hardcore: bool,
pub game_mode: GameMode,
/// Same values as `game_mode` but with -1 to indicate no previous.
pub previous_game_mode: i8,
pub dimension_names: Vec<Ident<Cow<'a, str>>>,
pub registry_codec: Cow<'a, Compound>,
pub dimension_type_name: Ident<Cow<'a, str>>,
pub dimension_name: Ident<Cow<'a, str>>,
pub hashed_seed: i64,
pub max_players: VarInt,
pub view_distance: VarInt,
pub simulation_distance: VarInt,
pub reduced_debug_info: bool,
pub enable_respawn_screen: bool,
pub is_debug: bool,
pub is_flat: bool,
pub last_death_location: Option<GlobalPos<'a>>,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::GAME_STATE_CHANGE_S2C)]
pub struct GameStateChangeS2c {
pub kind: GameEventKind,
pub value: f32,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum GameEventKind {
NoRespawnBlockAvailable,
EndRaining,
BeginRaining,
ChangeGameMode,
WinGame,
DemoEvent,
ArrowHitPlayer,
RainLevelChange,
ThunderLevelChange,
PlayPufferfishStingSound,
PlayElderGuardianMobAppearance,
EnableRespawnScreen,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::HEALTH_UPDATE_S2C)]
pub struct HealthUpdateS2c {
pub health: f32,
pub food: VarInt,
pub food_saturation: f32,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_ABILITIES_S2C)]
pub struct PlayerAbilitiesS2c {
pub flags: PlayerAbilitiesFlags,
pub flying_speed: f32,
pub fov_modifier: f32,
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
pub struct PlayerAbilitiesFlags {
pub invulnerable: bool,
pub flying: bool,
pub allow_flying: bool,
pub instant_break: bool,
#[bits(4)]
_pad: u8,
}
#[derive(Clone, PartialEq, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_RESPAWN_S2C)]
pub struct PlayerRespawnS2c<'a> {
pub dimension_type_name: Ident<Cow<'a, str>>,
pub dimension_name: Ident<Cow<'a, str>>,
pub hashed_seed: u64,
pub game_mode: GameMode,
pub previous_game_mode: i8,
pub is_debug: bool,
pub is_flat: bool,
pub copy_metadata: bool,
pub last_death_location: Option<GlobalPos<'a>>,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_SPAWN_POSITION_S2C)]
pub struct PlayerSpawnPositionS2c {
pub position: BlockPos,
pub angle: f32,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_SPAWN_S2C)]
pub struct PlayerSpawnS2c {
pub entity_id: VarInt,
pub player_uuid: Uuid,
pub position: DVec3,
pub yaw: ByteAngle,
pub pitch: ByteAngle,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::SERVER_METADATA_S2C)]
pub struct ServerMetadataS2c<'a> {
pub motd: Cow<'a, Text>,
pub icon: Option<&'a [u8]>,
pub enforce_secure_chat: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::SIGN_EDITOR_OPEN_S2C)]
pub struct SignEditorOpenS2c {
pub location: BlockPos,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::SIMULATION_DISTANCE_S2C)]
pub struct SimulationDistanceS2c {
pub simulation_distance: VarInt,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::STATISTICS_S2C)]
pub struct StatisticsS2c {
pub statistics: Vec<Statistic>,
}
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)]
pub struct Statistic {
pub category_id: VarInt,
pub statistic_id: VarInt,
pub value: VarInt,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::VEHICLE_MOVE_S2C)]
pub struct VehicleMoveS2c {
pub position: DVec3,
pub yaw: f32,
pub pitch: f32,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::OPEN_WRITTEN_BOOK_S2C)]
pub struct OpenWrittenBookS2c {
pub hand: Hand,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAY_PING_S2C)]
pub struct PlayPingS2c {
pub id: i32,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::NBT_QUERY_RESPONSE_S2C)]
pub struct NbtQueryResponseS2c {
pub transaction_id: VarInt,
pub nbt: Compound,
}

View file

@ -0,0 +1,89 @@
use std::borrow::Cow;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use valence_core::protocol::encode::WritePacket;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_core::text::Text;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
use crate::Client;
pub(super) fn build(app: &mut App) {
app.add_event::<ResourcePackStatusEvent>().add_system(
handle_resource_pack_status
.in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate),
);
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ResourcePackStatusEvent {
pub client: Entity,
pub status: ResourcePackStatus,
}
pub use ResourcePackStatusC2s as ResourcePackStatus;
impl Client {
/// Requests that the client download and enable a resource pack.
///
/// # Arguments
/// * `url` - The URL of the resource pack file.
/// * `hash` - The SHA-1 hash of the resource pack file. Any value other
/// than a 40-character hexadecimal string is ignored by the client.
/// * `forced` - Whether a client should be kicked from the server upon
/// declining the pack (this is enforced client-side)
/// * `prompt_message` - A message to be displayed with the resource pack
/// dialog.
pub fn set_resource_pack(
&mut self,
url: &str,
hash: &str,
forced: bool,
prompt_message: Option<Text>,
) {
self.write_packet(&ResourcePackSendS2c {
url,
hash,
forced,
prompt_message: prompt_message.map(|t| t.into()),
});
}
}
fn handle_resource_pack_status(
mut packets: EventReader<PacketEvent>,
mut events: EventWriter<ResourcePackStatusEvent>,
) {
for packet in packets.iter() {
if let Some(pkt) = packet.decode::<ResourcePackStatusC2s>() {
events.send(ResourcePackStatusEvent {
client: packet.client,
status: pkt,
})
}
}
}
#[derive(Clone, PartialEq, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::RESOURCE_PACK_SEND_S2C)]
pub struct ResourcePackSendS2c<'a> {
pub url: &'a str,
pub hash: &'a str,
pub forced: bool,
pub prompt_message: Option<Cow<'a, Text>>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::RESOURCE_PACK_STATUS_C2S)]
pub enum ResourcePackStatusC2s {
/// The client has successfully loaded the server's resource pack.
SuccessfullyLoaded,
/// The client has declined the server's resource pack.
Declined,
/// The client has failed to download the server's resource pack.
FailedDownload,
/// The client has accepted the server's resource pack.
Accepted,
}

View file

@ -1,9 +1,11 @@
pub use valence_core::packet::c2s::play::client_settings::ChatMode; use bevy_app::prelude::*;
use valence_core::packet::c2s::play::ClientSettingsC2s; use bevy_ecs::prelude::*;
pub use valence_entity::player::{MainArm, PlayerModelParts}; use bitfield_struct::bitfield;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use valence_entity::player::{self, PlayerModelParts};
use super::*;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
use crate::ViewDistance;
pub(super) fn build(app: &mut App) { pub(super) fn build(app: &mut App) {
app.add_system( app.add_system(
@ -13,6 +15,7 @@ pub(super) fn build(app: &mut App) {
); );
} }
/// Component containing client-controlled settings about a client.
#[derive(Component, Default, Debug)] #[derive(Component, Default, Debug)]
pub struct ClientSettings { pub struct ClientSettings {
pub locale: Box<str>, pub locale: Box<str>,
@ -28,7 +31,7 @@ fn handle_client_settings(
&mut ViewDistance, &mut ViewDistance,
&mut ClientSettings, &mut ClientSettings,
&mut PlayerModelParts, &mut PlayerModelParts,
&mut MainArm, &mut player::MainArm,
)>, )>,
) { ) {
for packet in packets.iter() { for packet in packets.iter() {
@ -45,8 +48,55 @@ fn handle_client_settings(
settings.allow_server_listings = pkt.allow_server_listings; settings.allow_server_listings = pkt.allow_server_listings;
model_parts.set_if_neq(PlayerModelParts(u8::from(pkt.displayed_skin_parts) as i8)); model_parts.set_if_neq(PlayerModelParts(u8::from(pkt.displayed_skin_parts) as i8));
main_arm.set_if_neq(MainArm(pkt.main_arm as i8)); main_arm.set_if_neq(player::MainArm(pkt.main_arm as i8));
} }
} }
} }
} }
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::CLIENT_SETTINGS_C2S)]
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_arm: MainArm,
pub enable_text_filtering: bool,
pub allow_server_listings: bool,
}
#[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, Default, Debug, Encode, Decode)]
pub enum ChatMode {
Enabled,
CommandsOnly,
#[default]
Hidden,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode)]
pub enum MainArm {
Left,
#[default]
Right,
}
impl From<MainArm> for player::MainArm {
fn from(value: MainArm) -> Self {
Self(value as i8)
}
}

View file

@ -0,0 +1,50 @@
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
pub(super) fn build(app: &mut App) {
app.add_event::<RequestRespawnEvent>()
.add_event::<RequestStatsEvent>()
.add_system(
handle_status
.in_schedule(EventLoopSchedule)
.in_base_set(EventLoopSet::PreUpdate),
);
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct RequestRespawnEvent {
pub client: Entity,
}
pub struct RequestStatsEvent {
pub client: Entity,
}
fn handle_status(
mut packets: EventReader<PacketEvent>,
mut respawn_events: EventWriter<RequestRespawnEvent>,
mut request_stats_events: EventWriter<RequestStatsEvent>,
) {
for packet in packets.iter() {
if let Some(pkt) = packet.decode::<ClientStatusC2s>() {
match pkt {
ClientStatusC2s::PerformRespawn => respawn_events.send(RequestRespawnEvent {
client: packet.client,
}),
ClientStatusC2s::RequestStats => request_stats_events.send(RequestStatsEvent {
client: packet.client,
}),
}
}
}
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::CLIENT_STATUS_C2S)]
pub enum ClientStatusC2s {
PerformRespawn,
RequestStats,
}

View file

@ -1,4 +1,7 @@
use valence_core::packet::c2s::play::TeleportConfirmC2s; use bitfield_struct::bitfield;
use glam::DVec3;
use valence_core::protocol::var_int::VarInt;
use valence_core::protocol::{packet_id, Decode, Encode, Packet};
use super::*; use super::*;
use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent}; use crate::event_loop::{EventLoopSchedule, EventLoopSet, PacketEvent};
@ -79,7 +82,7 @@ fn teleport(
yaw: if changed_yaw { look.yaw } else { 0.0 }, yaw: if changed_yaw { look.yaw } else { 0.0 },
pitch: if changed_pitch { look.pitch } else { 0.0 }, pitch: if changed_pitch { look.pitch } else { 0.0 },
flags, flags,
teleport_id: VarInt(state.teleport_id_counter as i32), teleport_id: (state.teleport_id_counter as i32).into(),
}); });
state.pending_teleports = state.pending_teleports.wrapping_add(1); state.pending_teleports = state.pending_teleports.wrapping_add(1);
@ -122,3 +125,31 @@ fn handle_teleport_confirmations(
} }
} }
} }
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::TELEPORT_CONFIRM_C2S)]
pub struct TeleportConfirmC2s {
pub teleport_id: VarInt,
}
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::PLAYER_POSITION_LOOK_S2C)]
pub struct PlayerPositionLookS2c {
pub position: DVec3,
pub yaw: f32,
pub pitch: f32,
pub flags: PlayerPositionLookFlags,
pub teleport_id: VarInt,
}
#[bitfield(u8)]
#[derive(PartialEq, Eq, Encode, Decode)]
pub struct PlayerPositionLookFlags {
pub x: bool,
pub y: bool,
pub z: bool,
pub y_rot: bool,
pub x_rot: bool,
#[bits(3)]
_pad: u8,
}

View file

@ -0,0 +1,94 @@
use valence_core::protocol::{packet_id, Decode, Encode};
use super::*;
pub trait SetTitle {
/// Displays a title to a 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
/// [`set_title_times`](Self::set_title_times).
fn set_title(&mut self, text: impl Into<Text>);
fn set_subtitle(&mut self, text: impl Into<Text>);
fn set_action_bar(&mut self, text: impl Into<Text>);
/// - `fade_in`: Ticks to spend fading in.
/// - `stay`: Ticks to keep the title displayed.
/// - `fade_out`: Ticks to spend fading out.
fn set_title_times(&mut self, fade_in: i32, stay: i32, fade_out: i32);
fn clear_title(&mut self);
fn reset_title(&mut self);
}
impl<T: WritePacket> SetTitle for T {
fn set_title(&mut self, text: impl Into<Text>) {
self.write_packet(&TitleS2c {
title_text: text.into().into(),
});
}
fn set_subtitle(&mut self, text: impl Into<Text>) {
self.write_packet(&SubtitleS2c {
subtitle_text: text.into().into(),
});
}
fn set_action_bar(&mut self, text: impl Into<Text>) {
self.write_packet(&OverlayMessageS2c {
action_bar_text: text.into().into(),
});
}
fn set_title_times(&mut self, fade_in: i32, stay: i32, fade_out: i32) {
self.write_packet(&TitleFadeS2c {
fade_in,
stay,
fade_out,
});
}
fn clear_title(&mut self) {
self.write_packet(&ClearTitleS2c { reset: false });
}
fn reset_title(&mut self) {
self.write_packet(&ClearTitleS2c { reset: true });
}
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::TITLE_S2C)]
pub struct TitleS2c<'a> {
pub title_text: Cow<'a, Text>,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::SUBTITLE_S2C)]
pub struct SubtitleS2c<'a> {
pub subtitle_text: Cow<'a, Text>,
}
#[derive(Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::OVERLAY_MESSAGE_S2C)]
pub struct OverlayMessageS2c<'a> {
pub action_bar_text: Cow<'a, Text>,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::TITLE_FADE_S2C)]
pub struct TitleFadeS2c {
pub fade_in: i32,
pub stay: i32,
pub fade_out: i32,
}
#[derive(Copy, Clone, Debug, Encode, Decode, Packet)]
#[packet(id = packet_id::CLEAR_TITLE_S2C)]
pub struct ClearTitleS2c {
pub reset: bool,
}

View file

@ -15,10 +15,8 @@
//! New joined players are handled, so that they are get weather events from //! New joined players are handled, so that they are get weather events from
//! the instance. //! the instance.
use valence_core::packet::s2c::play::game_state_change::GameEventKind;
use valence_core::packet::s2c::play::GameStateChangeS2c;
use super::*; use super::*;
use crate::packet::{GameEventKind, GameStateChangeS2c};
#[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)] #[derive(SystemSet, Copy, Clone, PartialEq, Eq, Hash, Debug)]
struct UpdateWeatherPerInstanceSet; struct UpdateWeatherPerInstanceSet;

View file

@ -1,4 +1,4 @@
use heck::ToPascalCase; use heck::ToShoutySnakeCase;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use serde::Deserialize; use serde::Deserialize;
@ -21,15 +21,14 @@ pub fn build() -> anyhow::Result<TokenStream> {
for packet in packets { for packet in packets {
let stripped_name = packet.name.strip_suffix("Packet").unwrap_or(&packet.name); let stripped_name = packet.name.strip_suffix("Packet").unwrap_or(&packet.name);
let name_ident = ident(stripped_name.replace('.', "_").to_pascal_case()); let name_ident = ident(stripped_name.to_shouty_snake_case());
let id = packet.id; let id = packet.id;
let doc = format!("Side: {}\nState: {}", packet.side, packet.state); let doc = format!("Side: {}\n\nState: {}", packet.side, packet.state);
consts.extend([quote! { consts.extend([quote! {
#[doc = #doc] #[doc = #doc]
#[allow(non_upper_case_globals)] pub const #name_ident: i32 = #id;
pub(crate) const #name_ident: i32 = #id;
}]); }]);
} }

View file

@ -3,7 +3,7 @@ use std::io::Write;
use anyhow::bail; use anyhow::bail;
use crate::direction::Direction; use crate::direction::Direction;
use crate::packet::{Decode, Encode}; use crate::protocol::{Decode, Encode};
/// Represents an absolute block position in world space. /// Represents an absolute block position in world space.
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, Default, PartialEq, Eq, Hash, Debug)]

View file

@ -1,7 +1,7 @@
use glam::DVec3; use glam::DVec3;
use crate::block_pos::BlockPos; use crate::block_pos::BlockPos;
use crate::packet::{Decode, Encode}; use crate::protocol::{Decode, Encode};
/// The X and Z position of a chunk. /// The X and Z position of a chunk.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug, Encode, Decode)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Debug, Encode, Decode)]

View file

@ -1,4 +1,4 @@
use crate::packet::{Decode, Encode}; use crate::protocol::{Decode, Encode};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode)]
pub enum Difficulty { pub enum Difficulty {

View file

@ -1,6 +1,6 @@
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use crate::packet::{Decode, Encode}; use crate::protocol::{Decode, Encode};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Component)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Encode, Decode, Component)]
pub enum Direction { pub enum Direction {

View file

@ -1,6 +1,6 @@
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use crate::packet::{Decode, Encode}; use crate::protocol::{Decode, Encode};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode, Component)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Encode, Decode, Component)]
pub enum GameMode { pub enum GameMode {

View file

@ -1,4 +1,4 @@
use crate::packet::{Decode, Encode}; use crate::protocol::{Decode, Encode};
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, Encode, Decode)] #[derive(Copy, Clone, PartialEq, Eq, Default, Debug, Encode, Decode)]
pub enum Hand { pub enum Hand {

View file

@ -11,7 +11,7 @@ use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error; use thiserror::Error;
use crate::packet::{Decode, Encode}; use crate::protocol::{Decode, Encode};
#[doc(hidden)] #[doc(hidden)]
pub mod __private { pub mod __private {

View file

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

View file

@ -28,11 +28,11 @@ pub mod game_mode;
pub mod hand; pub mod hand;
pub mod ident; pub mod ident;
pub mod item; pub mod item;
pub mod packet; pub mod particle;
pub mod player_textures; pub mod player_textures;
pub mod property; pub mod property;
pub mod protocol;
pub mod scratch; pub mod scratch;
pub mod sound;
pub mod text; pub mod text;
pub mod translation_key; pub mod translation_key;
pub mod uuid; pub mod uuid;
@ -51,8 +51,8 @@ use crate::despawn::despawn_marked_entities;
pub mod __private { pub mod __private {
pub use anyhow::{anyhow, bail, ensure, Context, Result}; pub use anyhow::{anyhow, bail, ensure, Context, Result};
pub use crate::packet::var_int::VarInt; pub use crate::protocol::var_int::VarInt;
pub use crate::packet::{Decode, Encode, Packet}; pub use crate::protocol::{Decode, Encode, Packet};
} }
// Needed to make proc macros work. // Needed to make proc macros work.

View file

@ -1,654 +0,0 @@
//! Types and functions used in Minecraft's packets. Structs for each packet are
//! defined here too.
//!
//! Client-to-server packets live in [`c2s`] while server-to-client packets are
//! in [`s2c`].
pub mod array;
pub mod byte_angle;
pub mod decode;
pub mod encode;
pub mod global_pos;
pub mod impls;
pub mod message_signature;
pub mod raw;
pub mod var_int;
pub mod var_long;
use std::io::Write;
pub use valence_core_macros::{Decode, Encode, Packet};
/// The maximum number of bytes in a single Minecraft packet.
pub const MAX_PACKET_SIZE: i32 = 2097152;
/// The `Encode` trait allows objects to be written to the Minecraft protocol.
/// It is the inverse of [`Decode`].
///
/// # Deriving
///
/// This trait can be implemented automatically for structs and enums by using
/// the [`Encode`][macro] derive macro. All components of the type must
/// implement `Encode`. Components are encoded in the order they appear in the
/// type definition.
///
/// For enums, the variant to encode is marked by a leading [`VarInt`]
/// discriminant (tag). The discriminant value can be changed using the `#[tag =
/// ...]` attribute on the variant in question. Discriminant values are assigned
/// to variants using rules similar to regular enum discriminants.
///
/// ```
/// use valence_core::packet::Encode;
///
/// #[derive(Encode)]
/// struct MyStruct<'a> {
/// first: i32,
/// second: &'a str,
/// third: [f64; 3],
/// }
///
/// #[derive(Encode)]
/// enum MyEnum {
/// First, // tag = 0
/// Second, // tag = 1
/// #[tag = 25]
/// Third, // tag = 25
/// Fourth, // tag = 26
/// }
///
/// let value = MyStruct {
/// first: 10,
/// second: "hello",
/// third: [1.5, 3.14, 2.718],
/// };
///
/// let mut buf = vec![];
/// value.encode(&mut buf).unwrap();
///
/// println!("{buf:?}");
/// ```
///
/// [macro]: valence_core_macros::Encode
/// [`VarInt`]: var_int::VarInt
pub trait Encode {
/// Writes this object to the provided writer.
///
/// If this type also implements [`Decode`] then successful calls to this
/// function returning `Ok(())` must always successfully [`decode`] using
/// the data that was written to the writer. The exact number of bytes
/// that were originally written must be consumed during the decoding.
///
/// [`decode`]: Decode::decode
fn encode(&self, w: impl Write) -> anyhow::Result<()>;
/// Like [`Encode::encode`], except that a whole slice of values is encoded.
///
/// This method must be semantically equivalent to encoding every element of
/// the slice in sequence with no leading length prefix (which is exactly
/// what the default implementation does), but a more efficient
/// implementation may be used.
///
/// This optimization is very important for some types like `u8` where
/// [`write_all`] is used. Because impl specialization is unavailable in
/// stable Rust, we must make the slice specialization part of this trait.
///
/// [`write_all`]: Write::write_all
fn encode_slice(slice: &[Self], mut w: impl Write) -> anyhow::Result<()>
where
Self: Sized,
{
for value in slice {
value.encode(&mut w)?;
}
Ok(())
}
}
/// The `Decode` trait allows objects to be read from the Minecraft protocol. It
/// is the inverse of [`Encode`].
///
/// `Decode` is parameterized by a lifetime. This allows the decoded value to
/// borrow data from the byte slice it was read from.
///
/// # Deriving
///
/// This trait can be implemented automatically for structs and enums by using
/// the [`Decode`][macro] derive macro. All components of the type must
/// implement `Decode`. Components are decoded in the order they appear in the
/// type definition.
///
/// For enums, the variant to decode is determined by a leading [`VarInt`]
/// discriminant (tag). The discriminant value can be changed using the `#[tag =
/// ...]` attribute on the variant in question. Discriminant values are assigned
/// to variants using rules similar to regular enum discriminants.
///
/// ```
/// use valence_core::packet::Decode;
///
/// #[derive(PartialEq, Debug, Decode)]
/// struct MyStruct {
/// first: i32,
/// second: MyEnum,
/// }
///
/// #[derive(PartialEq, Debug, Decode)]
/// enum MyEnum {
/// First, // tag = 0
/// Second, // tag = 1
/// #[tag = 25]
/// Third, // tag = 25
/// Fourth, // tag = 26
/// }
///
/// let mut r: &[u8] = &[0, 0, 0, 0, 26];
///
/// let value = MyStruct::decode(&mut r).unwrap();
/// let expected = MyStruct {
/// first: 0,
/// second: MyEnum::Fourth,
/// };
///
/// assert_eq!(value, expected);
/// assert!(r.is_empty());
/// ```
///
/// [macro]: valence_core_macros::Decode
/// [`VarInt`]: var_int::VarInt
pub trait Decode<'a>: Sized {
/// Reads this object from the provided byte slice.
///
/// Implementations of `Decode` are expected to shrink the slice from the
/// front as bytes are read.
fn decode(r: &mut &'a [u8]) -> anyhow::Result<Self>;
}
/// Like [`Encode`] + [`Decode`], but implementations must read and write a
/// leading [`VarInt`] packet ID before any other data.
///
/// # Deriving
///
/// This trait can be implemented automatically by using the
/// [`Packet`][macro] derive macro. The trait is implemented by reading or
/// writing the packet ID provided in the `#[packet_id = ...]` helper attribute
/// followed by a call to [`Encode::encode`] or [`Decode::decode`]. The target
/// type must implement [`Encode`], [`Decode`], and [`std::fmt::Debug`].
///
/// ```
/// use valence_core::packet::{Decode, Encode, Packet};
///
/// #[derive(Encode, Decode, Packet, Debug)]
/// #[packet_id = 42]
/// struct MyStruct {
/// first: i32,
/// }
///
/// let value = MyStruct { first: 123 };
/// let mut buf = vec![];
///
/// value.encode_packet(&mut buf).unwrap();
/// println!("{buf:?}");
/// ```
///
/// [macro]: valence_core_macros::Packet
/// [`VarInt`]: var_int::VarInt
pub trait Packet<'a>: Sized + std::fmt::Debug {
/// The packet returned by [`Self::packet_id`]. If the packet ID is not
/// statically known, then a negative value is used instead.
const PACKET_ID: i32 = -1;
/// Returns the ID of this packet.
fn packet_id(&self) -> i32;
/// Returns the name of this packet, typically without whitespace or
/// additional formatting.
fn packet_name(&self) -> &str;
/// Like [`Encode::encode`], but a leading [`VarInt`] packet ID must be
/// written by this method first.
///
/// [`VarInt`]: var_int::VarInt
fn encode_packet(&self, w: impl Write) -> anyhow::Result<()>;
/// Like [`Decode::decode`], but a leading [`VarInt`] packet ID must be read
/// by this method first.
///
/// [`VarInt`]: var_int::VarInt
fn decode_packet(r: &mut &'a [u8]) -> anyhow::Result<Self>;
}
/// Defines an enum of packets and implements [`Packet`] for each.
macro_rules! packet_group {
(
$(#[$attrs:meta])*
$enum_name:ident<$enum_life:lifetime> {
$($packet:ident $(<$life:lifetime>)?),* $(,)?
}
) => {
$(#[$attrs])*
pub enum $enum_name<$enum_life> {
$(
$packet($packet $(<$life>)?),
)*
}
$(
impl<$enum_life> From<$packet $(<$life>)?> for $enum_name<$enum_life> {
fn from(p: $packet $(<$life>)?) -> Self {
Self::$packet(p)
}
}
impl<$enum_life> $crate::packet::Packet<$enum_life> for $packet$(<$life>)? {
const PACKET_ID: i32 = $crate::packet::id::$packet;
fn packet_id(&self) -> i32 {
Self::PACKET_ID
}
fn packet_name(&self) -> &str {
stringify!($packet)
}
#[allow(unused_imports)]
fn encode_packet(&self, mut w: impl std::io::Write) -> $crate::__private::Result<()> {
use $crate::__private::*;
VarInt(Self::PACKET_ID)
.encode(&mut w)
.context("failed to encode packet ID")?;
self.encode(w)
}
#[allow(unused_imports)]
fn decode_packet(r: &mut &$enum_life [u8]) -> $crate::__private::Result<Self> {
use $crate::__private::*;
let id = VarInt::decode(r).context("failed to decode packet ID")?.0;
ensure!(id == Self::PACKET_ID, "unexpected packet ID {} (expected {})", id, Self::PACKET_ID);
Self::decode(r)
}
}
)*
impl<$enum_life> $crate::packet::Packet<$enum_life> for $enum_name<$enum_life> {
fn packet_id(&self) -> i32 {
match self {
$(
Self::$packet(_) => <$packet as $crate::packet::Packet>::PACKET_ID,
)*
}
}
fn packet_name(&self) -> &str {
match self {
$(
Self::$packet(pkt) => pkt.packet_name(),
)*
}
}
fn encode_packet(&self, mut w: impl std::io::Write) -> $crate::__private::Result<()> {
use $crate::__private::*;
match self {
$(
Self::$packet(pkt) => {
VarInt(<$packet as Packet>::PACKET_ID).encode(&mut w)?;
pkt.encode(w)?;
}
)*
}
Ok(())
}
fn decode_packet(r: &mut &$enum_life [u8]) -> $crate::__private::Result<Self> {
use $crate::__private::*;
let id = VarInt::decode(r)?.0;
Ok(match id {
$(
<$packet as Packet>::PACKET_ID =>
Self::$packet($packet::decode(r)?),
)*
id => bail!("unknown packet ID {} while decoding {}", id, stringify!($enum_name)),
})
}
}
impl<$enum_life> std::fmt::Debug for $enum_name<$enum_life> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(
Self::$packet(pkt) => pkt.fmt(f),
)*
}
}
}
};
// No lifetime on the enum in this case.
(
$(#[$attrs:meta])*
$enum_name:ident {
$($packet:ident),* $(,)?
}
) => {
$(#[$attrs])*
pub enum $enum_name {
$(
$packet($packet),
)*
}
$(
impl From<$packet> for $enum_name {
fn from(p: $packet) -> Self {
Self::$packet(p)
}
}
impl $crate::__private::Packet<'_> for $packet {
const PACKET_ID: i32 = $crate::packet::id::$packet;
fn packet_id(&self) -> i32 {
Self::PACKET_ID
}
fn packet_name(&self) -> &str {
stringify!($packet)
}
#[allow(unused_imports)]
fn encode_packet(&self, mut w: impl std::io::Write) -> $crate::__private::Result<()> {
use $crate::__private::*;
VarInt(Self::PACKET_ID)
.encode(&mut w)
.context("failed to encode packet ID")?;
self.encode(w)
}
#[allow(unused_imports)]
fn decode_packet(r: &mut &[u8]) -> $crate::__private::Result<Self> {
use $crate::__private::*;
let id = VarInt::decode(r).context("failed to decode packet ID")?.0;
ensure!(id == Self::PACKET_ID, "unexpected packet ID {} (expected {})", id, Self::PACKET_ID);
Self::decode(r)
}
}
)*
impl $crate::__private::Packet<'_> for $enum_name {
fn packet_id(&self) -> i32 {
use $crate::__private::*;
match self {
$(
Self::$packet(_) => <$packet as Packet>::PACKET_ID,
)*
}
}
fn packet_name(&self) -> &str {
match self {
$(
Self::$packet(pkt) => pkt.packet_name(),
)*
}
}
fn encode_packet(&self, mut w: impl std::io::Write) -> $crate::__private::Result<()> {
use $crate::__private::*;
match self {
$(
Self::$packet(pkt) => {
VarInt(<$packet as Packet>::PACKET_ID).encode(&mut w)?;
pkt.encode(w)?;
}
)*
}
Ok(())
}
fn decode_packet(r: &mut &[u8]) -> $crate::__private::Result<Self> {
use $crate::__private::*;
let id = VarInt::decode(r)?.0;
Ok(match id {
$(
<$packet as Packet>::PACKET_ID =>
Self::$packet($packet::decode(r)?),
)*
id => anyhow::bail!("unknown packet ID {} while decoding {}", id, stringify!($enum_name)),
})
}
}
impl std::fmt::Debug for $enum_name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(
Self::$packet(pkt) => pkt.fmt(f),
)*
}
}
}
}
}
pub mod c2s;
pub mod s2c;
/// Contains the packet ID for every packet. Because the constants are private
/// to the crate, the compiler will yell at us when we forget to use one.
mod id {
include!(concat!(env!("OUT_DIR"), "/packet_id.rs"));
}
#[allow(dead_code)]
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use bytes::BytesMut;
use super::*;
use crate::packet::c2s::play::{C2sPlayPacket, HandSwingC2s};
use crate::packet::decode::{decode_packet, PacketDecoder};
use crate::packet::encode::PacketEncoder;
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 1]
struct RegularStruct {
foo: i32,
bar: bool,
baz: f64,
}
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 2]
struct UnitStruct;
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 3]
struct EmptyStruct {}
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 4]
struct TupleStruct(i32, bool, f64);
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 5]
struct StructWithGenerics<'z, T = ()> {
foo: &'z str,
bar: T,
}
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 6]
struct TupleStructWithGenerics<'z, T = ()>(&'z str, i32, T);
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 7]
enum RegularEnum {
Empty,
Tuple(i32, bool, f64),
Fields { foo: i32, bar: bool, baz: f64 },
}
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 8]
enum EmptyEnum {}
#[derive(Encode, Decode, Packet, Debug)]
#[packet_id = 0xbeef]
enum EnumWithGenericsAndTags<'z, T = ()> {
#[tag = 5]
First {
foo: &'z str,
},
Second(&'z str),
#[tag = 0xff]
Third,
#[tag = 0]
Fourth(T),
}
#[allow(unconditional_recursion, clippy::extra_unused_type_parameters)]
fn assert_has_impls<'a, T>()
where
T: Encode + Decode<'a> + Packet<'a>,
{
assert_has_impls::<RegularStruct>();
assert_has_impls::<UnitStruct>();
assert_has_impls::<EmptyStruct>();
assert_has_impls::<TupleStruct>();
assert_has_impls::<StructWithGenerics>();
assert_has_impls::<TupleStructWithGenerics>();
assert_has_impls::<RegularEnum>();
assert_has_impls::<EmptyEnum>();
assert_has_impls::<EnumWithGenericsAndTags>();
}
#[test]
fn packet_name() {
assert_eq!(UnitStruct.packet_name(), "UnitStruct");
assert_eq!(RegularEnum::Empty.packet_name(), "RegularEnum");
assert_eq!(
StructWithGenerics {
foo: "blah",
bar: ()
}
.packet_name(),
"StructWithGenerics"
);
assert_eq!(
C2sPlayPacket::HandSwingC2s(HandSwingC2s {
hand: Default::default()
})
.packet_name(),
"HandSwingC2s"
);
}
use crate::block_pos::BlockPos;
use crate::hand::Hand;
use crate::ident::Ident;
use crate::item::{ItemKind, ItemStack};
use crate::packet::var_int::VarInt;
use crate::packet::var_long::VarLong;
use crate::text::{Text, TextFormat};
#[cfg(feature = "encryption")]
const CRYPT_KEY: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
#[derive(PartialEq, Debug, Encode, Decode, Packet)]
#[packet_id = 42]
struct TestPacket<'a> {
a: bool,
b: u8,
c: i32,
d: f32,
e: f64,
f: BlockPos,
g: Hand,
h: Ident<Cow<'a, str>>,
i: Option<ItemStack>,
j: Text,
k: VarInt,
l: VarLong,
m: &'a str,
n: &'a [u8; 10],
o: [u128; 3],
}
impl<'a> TestPacket<'a> {
fn new(string: &'a str) -> Self {
Self {
a: true,
b: 12,
c: -999,
d: 5.001,
e: 1e10,
f: BlockPos::new(1, 2, 3),
g: Hand::Off,
h: Ident::new("minecraft:whatever").unwrap(),
i: Some(ItemStack::new(ItemKind::WoodenSword, 12, None)),
j: "my ".into_text() + "fancy".italic() + " text",
k: VarInt(123),
l: VarLong(456),
m: string,
n: &[7; 10],
o: [123456789; 3],
}
}
}
fn check_test_packet(dec: &mut PacketDecoder, string: &str) {
let frame = dec.try_next_packet().unwrap().unwrap();
let pkt = decode_packet::<TestPacket>(&frame).unwrap();
assert_eq!(&pkt, &TestPacket::new(string));
}
#[test]
fn packets_round_trip() {
let mut buf = BytesMut::new();
let mut enc = PacketEncoder::new();
enc.append_packet(&TestPacket::new("first")).unwrap();
#[cfg(feature = "compression")]
enc.set_compression(Some(0));
enc.append_packet(&TestPacket::new("second")).unwrap();
buf.unsplit(enc.take());
#[cfg(feature = "encryption")]
enc.enable_encryption(&CRYPT_KEY);
enc.append_packet(&TestPacket::new("third")).unwrap();
enc.prepend_packet(&TestPacket::new("fourth")).unwrap();
buf.unsplit(enc.take());
let mut dec = PacketDecoder::new();
dec.queue_bytes(buf);
check_test_packet(&mut dec, "first");
#[cfg(feature = "compression")]
dec.set_compression(Some(0));
check_test_packet(&mut dec, "second");
#[cfg(feature = "encryption")]
dec.enable_encryption(&CRYPT_KEY);
check_test_packet(&mut dec, "fourth");
check_test_packet(&mut dec, "third");
}
}

View file

@ -1,205 +0,0 @@
pub mod handshake {
pub use handshake::HandshakeC2s;
#[allow(clippy::module_inception)]
pub mod handshake;
packet_group! {
#[derive(Clone)]
C2sHandshakePacket<'a> {
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 {
QueryPingC2s,
QueryRequestC2s,
}
}
}
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> {
LoginHelloC2s<'a>,
LoginKeyC2s<'a>,
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_block::PlayerInteractBlockC2s;
pub use player_interact_entity::PlayerInteractEntityC2s;
pub use player_interact_item::PlayerInteractItemC2s;
pub use player_move::{Full, LookAndOnGround, OnGroundOnly, PositionAndOnGround};
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_block;
pub mod player_interact_entity;
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> {
AdvancementTabC2s<'a>,
BoatPaddleStateC2s,
BookUpdateC2s<'a>,
ButtonClickC2s,
ChatMessageC2s<'a>,
ClickSlotC2s,
ClientCommandC2s,
ClientSettingsC2s<'a>,
ClientStatusC2s,
CloseHandledScreenC2s,
CommandExecutionC2s<'a>,
CraftRequestC2s<'a>,
CreativeInventoryActionC2s,
CustomPayloadC2s<'a>,
Full,
HandSwingC2s,
JigsawGeneratingC2s,
KeepAliveC2s,
LookAndOnGround,
MessageAcknowledgmentC2s,
OnGroundOnly,
PickFromInventoryC2s,
PlayerActionC2s,
PlayerInputC2s,
PlayerInteractBlockC2s,
PlayerInteractEntityC2s,
PlayerInteractItemC2s,
PlayerSessionC2s<'a>,
PlayPongC2s,
PositionAndOnGround,
QueryBlockNbtC2s,
QueryEntityNbtC2s,
RecipeBookDataC2s<'a>,
RecipeCategoryOptionsC2s,
RenameItemC2s<'a>,
RequestCommandCompletionsC2s<'a>,
ResourcePackStatusC2s,
SelectMerchantTradeC2s,
SpectatorTeleportC2s,
TeleportConfirmC2s,
UpdateBeaconC2s,
UpdateCommandBlockC2s<'a>,
UpdateCommandBlockMinecartC2s<'a>,
UpdateDifficultyC2s,
UpdateDifficultyLockC2s,
UpdateJigsawC2s<'a>,
UpdatePlayerAbilitiesC2s,
UpdateSelectedSlotC2s,
UpdateSignC2s<'a>,
UpdateStructureBlockC2s<'a>,
VehicleMoveC2s,
}
}
}

View file

@ -1,18 +0,0 @@
use crate::packet::var_int::VarInt;
use crate::packet::{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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +0,0 @@
use crate::packet::var_int::VarInt;
use crate::packet::{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

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

View file

@ -1,15 +0,0 @@
use crate::packet::var_int::VarInt;
use crate::packet::{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

@ -1,33 +0,0 @@
use crate::item::ItemStack;
use crate::packet::var_int::VarInt;
use crate::packet::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct ClickSlotC2s {
pub window_id: u8,
pub state_id: VarInt,
pub slot_idx: i16,
/// The button used to click the slot. An enum can't easily be used for this
/// because the meaning of this value depends on the mode.
pub button: i8,
pub mode: ClickMode,
pub slot_changes: 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

@ -1,22 +0,0 @@
use crate::packet::var_int::VarInt;
use crate::packet::{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

@ -1,43 +0,0 @@
use bitfield_struct::bitfield;
use crate::packet::{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_arm: MainArm,
pub enable_text_filtering: bool,
pub allow_server_listings: bool,
}
#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, Encode, Decode)]
pub enum ChatMode {
Enabled,
CommandsOnly,
#[default]
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 MainArm {
Left,
#[default]
Right,
}

View file

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

View file

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

View file

@ -1,21 +0,0 @@
use crate::packet::var_int::VarInt;
use crate::packet::{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

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

View file

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

View file

@ -1,11 +0,0 @@
use std::borrow::Cow;
use crate::ident::Ident;
use crate::packet::raw::RawBytes;
use crate::packet::{Decode, Encode};
#[derive(Clone, Debug, Encode, Decode)]
pub struct CustomPayloadC2s<'a> {
pub channel: Ident<Cow<'a, str>>,
pub data: RawBytes<'a>,
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,23 +0,0 @@
use crate::block_pos::BlockPos;
use crate::direction::Direction;
use crate::packet::var_int::VarInt;
use crate::packet::{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

@ -1,19 +0,0 @@
use bitfield_struct::bitfield;
use crate::packet::{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

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

View file

@ -1,19 +0,0 @@
use glam::Vec3;
use crate::hand::Hand;
use crate::packet::var_int::VarInt;
use crate::packet::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PlayerInteractEntityC2s {
pub entity_id: VarInt,
pub interact: EntityInteraction,
pub sneaking: bool,
}
#[derive(Copy, Clone, PartialEq, Debug, Encode, Decode)]
pub enum EntityInteraction {
Interact(Hand),
Attack,
InteractAt { target: Vec3, hand: Hand },
}

View file

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

View file

@ -1,29 +0,0 @@
use glam::DVec3;
use crate::packet::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct PositionAndOnGround {
pub position: DVec3,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct Full {
pub position: DVec3,
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct LookAndOnGround {
pub yaw: f32,
pub pitch: f32,
pub on_ground: bool,
}
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct OnGroundOnly {
pub on_ground: bool,
}

View file

@ -1,12 +0,0 @@
use uuid::Uuid;
use crate::packet::{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

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

View file

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

View file

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

View file

@ -1,16 +0,0 @@
use crate::packet::{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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +0,0 @@
use crate::packet::var_int::VarInt;
use crate::packet::{Decode, Encode};
#[derive(Copy, Clone, Debug, Encode, Decode)]
pub struct UpdateBeaconC2s {
pub primary_effect: Option<VarInt>,
pub secondary_effect: Option<VarInt>,
}

View file

@ -1,29 +0,0 @@
use bitfield_struct::bitfield;
use crate::block_pos::BlockPos;
use crate::packet::{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,
}

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