diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index df04a606..19e13253 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -16,6 +16,7 @@ pub mod ping; pub mod player; mod plugins; +#[cfg(feature = "log")] #[doc(hidden)] pub mod test_utils; diff --git a/azalea-client/src/plugins/attack.rs b/azalea-client/src/plugins/attack.rs index 86ed5de5..ec4337e5 100644 --- a/azalea-client/src/plugins/attack.rs +++ b/azalea-client/src/plugins/attack.rs @@ -90,6 +90,7 @@ impl Client { pub struct AttackQueued { pub target: Entity, } +#[allow(clippy::type_complexity)] pub fn handle_attack_queued( mut commands: Commands, mut query: Query<( diff --git a/azalea-client/src/plugins/loading.rs b/azalea-client/src/plugins/loading.rs index 217d6f75..b195868b 100644 --- a/azalea-client/src/plugins/loading.rs +++ b/azalea-client/src/plugins/loading.rs @@ -14,8 +14,8 @@ impl Plugin for PlayerLoadedPlugin { GameTick, player_loaded_packet .after(PhysicsSet) - .after(MiningSet) - .after(crate::movement::send_position), + .before(MiningSet) + .before(crate::movement::send_position), ); } } @@ -36,8 +36,11 @@ pub fn player_loaded_packet( Entity, ( With, - With, Without, + // the vanilla client waits for the chunk mesh to be "compiled" for the renderer (or + // some other conditions) before sending PlayerLoaded. see LevelLoadStatusManager.tick + // in the decompiled source + With, ), >, ) { diff --git a/azalea-client/src/plugins/movement.rs b/azalea-client/src/plugins/movement.rs index b4649f20..5d43261f 100644 --- a/azalea-client/src/plugins/movement.rs +++ b/azalea-client/src/plugins/movement.rs @@ -6,14 +6,17 @@ use azalea_entity::{ metadata::Sprinting, }; use azalea_physics::{PhysicsSet, ai_step}; -use azalea_protocol::packets::{ - Packet, - game::{ - ServerboundPlayerCommand, ServerboundPlayerInput, - s_move_player_pos::ServerboundMovePlayerPos, - s_move_player_pos_rot::ServerboundMovePlayerPosRot, - s_move_player_rot::ServerboundMovePlayerRot, - s_move_player_status_only::ServerboundMovePlayerStatusOnly, +use azalea_protocol::{ + common::movements::MoveFlags, + packets::{ + Packet, + game::{ + ServerboundPlayerCommand, ServerboundPlayerInput, + s_move_player_pos::ServerboundMovePlayerPos, + s_move_player_pos_rot::ServerboundMovePlayerPosRot, + s_move_player_rot::ServerboundMovePlayerRot, + s_move_player_status_only::ServerboundMovePlayerStatusOnly, + }, }, }; use azalea_world::{MinecraftEntityId, MoveEntityError}; @@ -186,12 +189,16 @@ pub fn send_position( // if self.is_passenger() { // TODO: posrot packet for being a passenger // } + let flags = MoveFlags { + on_ground: physics.on_ground(), + horizontal_collision: physics.horizontal_collision, + }; let packet = if sending_position && sending_direction { Some( ServerboundMovePlayerPosRot { pos: **position, look_direction: *direction, - on_ground: physics.on_ground(), + flags, } .into_variant(), ) @@ -199,7 +206,7 @@ pub fn send_position( Some( ServerboundMovePlayerPos { pos: **position, - on_ground: physics.on_ground(), + flags, } .into_variant(), ) @@ -207,17 +214,12 @@ pub fn send_position( Some( ServerboundMovePlayerRot { look_direction: *direction, - on_ground: physics.on_ground(), + flags, } .into_variant(), ) } else if physics.last_on_ground() != physics.on_ground() { - Some( - ServerboundMovePlayerStatusOnly { - on_ground: physics.on_ground(), - } - .into_variant(), - ) + Some(ServerboundMovePlayerStatusOnly { flags }.into_variant()) } else { None }; diff --git a/azalea-client/src/plugins/packet/game/mod.rs b/azalea-client/src/plugins/packet/game/mod.rs index 1ea4db10..d9940937 100644 --- a/azalea-client/src/plugins/packet/game/mod.rs +++ b/azalea-client/src/plugins/packet/game/mod.rs @@ -12,7 +12,10 @@ use azalea_entity::{ indexing::{EntityIdIndex, EntityUuidIndex}, metadata::{Health, apply_metadata}, }; -use azalea_protocol::packets::{ConnectionProtocol, game::*}; +use azalea_protocol::{ + common::movements::MoveFlags, + packets::{ConnectionProtocol, game::*}, +}; use azalea_world::{InstanceContainer, InstanceName, MinecraftEntityId, PartialInstance}; use bevy_ecs::{prelude::*, system::SystemState}; pub use events::*; @@ -319,15 +322,6 @@ impl GamePacketHandler<'_> { .entity(self.player) .insert(LoadedBy(HashSet::from_iter(vec![self.player]))); } - - // send the client information that we have set - debug!( - "Sending client information because login: {:?}", - client_information - ); - commands.trigger(SendPacketEvent::new(self.player, - azalea_protocol::packets::game::s_client_information::ServerboundClientInformation { client_information: client_information.clone() }, - )); }, ); } @@ -443,8 +437,7 @@ impl GamePacketHandler<'_> { ServerboundMovePlayerPosRot { pos: **position, look_direction: *direction, - // this is always false - on_ground: false, + flags: MoveFlags::default(), }, )); }); diff --git a/azalea-client/tests/packet_order.rs b/azalea-client/tests/packet_order.rs new file mode 100644 index 00000000..1d3b29a2 --- /dev/null +++ b/azalea-client/tests/packet_order.rs @@ -0,0 +1,150 @@ +use std::{collections::VecDeque, sync::Arc}; + +use azalea_client::{packet::game::SendPacketEvent, test_utils::prelude::*}; +use azalea_core::{ + position::{ChunkPos, Vec3}, + resource_location::ResourceLocation, +}; +use azalea_entity::LookDirection; +use azalea_protocol::{ + common::movements::{PositionMoveRotation, RelativeMovements}, + packets::{ + ConnectionProtocol, + config::{ClientboundFinishConfiguration, ClientboundRegistryData}, + game::{ClientboundPlayerPosition, ServerboundAcceptTeleportation, ServerboundGamePacket}, + }, +}; +use azalea_registry::{DataRegistry, DimensionType}; +use bevy_ecs::observer::Trigger; +use parking_lot::Mutex; +use simdnbt::owned::{NbtCompound, NbtTag}; + +#[test] +fn test_set_health_before_login() { + init_tracing(); + + let mut simulation = Simulation::new(ConnectionProtocol::Configuration); + let sent_packets = SentPackets::new(&mut simulation); + + simulation.receive_packet(ClientboundRegistryData { + registry_id: ResourceLocation::new("minecraft:dimension_type"), + entries: vec![( + ResourceLocation::new("minecraft:overworld"), + Some(NbtCompound::from_values(vec![ + ("height".into(), NbtTag::Int(384)), + ("min_y".into(), NbtTag::Int(-64)), + ])), + )] + .into_iter() + .collect(), + }); + simulation.tick(); + simulation.receive_packet(ClientboundFinishConfiguration); + simulation.tick(); + + simulation.receive_packet(make_basic_login_packet( + DimensionType::new_raw(0), // overworld + ResourceLocation::new("minecraft:overworld"), + )); + simulation.tick(); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // receive a chunk so the player is "loaded" now + simulation.receive_packet(make_basic_empty_chunk(ChunkPos::new(0, 0), (384 + 64) / 16)); + simulation.receive_packet(ClientboundPlayerPosition { + id: 1, + change: PositionMoveRotation { + pos: Vec3::new(1., 2., 3.), + delta: Vec3::ZERO, + look_direction: LookDirection::default(), + }, + relative: RelativeMovements::all_absolute(), + }); + simulation.tick(); + println!("sent_packets: {:?}", sent_packets.list.lock()); + sent_packets.expect("AcceptTeleportation", |p| { + matches!( + p, + ServerboundGamePacket::AcceptTeleportation(ServerboundAcceptTeleportation { id: 1 }) + ) + }); + sent_packets.expect("MovePlayerPosRot", |p| { + matches!(p, ServerboundGamePacket::MovePlayerPosRot(_)) + }); + + // in vanilla these might be sent in a later tick (depending on how long it + // takes to render the chunks)... see the comment in player_loaded_packet. + // this might be worth changing later for better anticheat compat? + sent_packets.expect("PlayerLoaded", |p| { + matches!(p, ServerboundGamePacket::PlayerLoaded(_)) + }); + sent_packets.expect("MovePlayerPos", |p| { + matches!(p, ServerboundGamePacket::MovePlayerPos(_)) + }); + + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); +} + +#[derive(Clone)] +pub struct SentPackets { + list: Arc>>, +} +impl SentPackets { + pub fn new(simulation: &mut Simulation) -> Self { + let sent_packets = SentPackets { + list: Default::default(), + }; + + let simulation_entity = simulation.entity; + let sent_packets_clone = sent_packets.clone(); + simulation + .app + .add_observer(move |trigger: Trigger| { + if trigger.sent_by == simulation_entity { + sent_packets_clone + .list + .lock() + .push_back(trigger.event().packet.clone()) + } + }); + + sent_packets + } + + pub fn clear(&self) { + self.list.lock().clear(); + } + + pub fn expect_tick_end(&self) { + self.expect("TickEnd", |p| { + matches!(p, ServerboundGamePacket::ClientTickEnd(_)) + }); + } + pub fn expect_empty(&self) { + let sent_packet = self.next(); + if let None = sent_packet { + } else { + panic!("Expected no packet, got {sent_packet:?}"); + } + } + pub fn expect( + &self, + expected_formatted: &str, + check: impl FnOnce(&ServerboundGamePacket) -> bool, + ) { + let sent_packet = self.next(); + if let Some(sent_packet) = sent_packet { + if !check(&sent_packet) { + panic!("Expected {expected_formatted}, got {sent_packet:?}"); + } + } else { + panic!("Expected {expected_formatted}, got nothing"); + } + } + pub fn next(&self) -> Option { + self.list.lock().pop_front() + } +} diff --git a/azalea-protocol/src/common/movements.rs b/azalea-protocol/src/common/movements.rs index a70342b3..e88fb87e 100644 --- a/azalea-protocol/src/common/movements.rs +++ b/azalea-protocol/src/common/movements.rs @@ -131,3 +131,33 @@ impl AzaleaWrite for RelativeMovements { set.azalea_write(buf) } } + +#[derive(Clone, Copy, Debug, Default)] +pub struct MoveFlags { + pub on_ground: bool, + pub horizontal_collision: bool, +} +impl AzaleaWrite for MoveFlags { + fn azalea_write(&self, buf: &mut impl io::Write) -> Result<(), io::Error> { + let mut bitset = FixedBitSet::<8>::new(); + if self.on_ground { + bitset.set(0); + } + if self.horizontal_collision { + bitset.set(1); + } + bitset.azalea_write(buf)?; + Ok(()) + } +} +impl AzaleaRead for MoveFlags { + fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result { + let bitset = FixedBitSet::<8>::azalea_read(buf)?; + let on_ground = bitset.index(0); + let horizontal_collision = bitset.index(1); + Ok(Self { + on_ground, + horizontal_collision, + }) + } +} diff --git a/azalea-protocol/src/packets/game/s_move_player_pos.rs b/azalea-protocol/src/packets/game/s_move_player_pos.rs index 2daf1d42..fd1f4a9f 100644 --- a/azalea-protocol/src/packets/game/s_move_player_pos.rs +++ b/azalea-protocol/src/packets/game/s_move_player_pos.rs @@ -2,8 +2,10 @@ use azalea_buf::AzBuf; use azalea_core::position::Vec3; use azalea_protocol_macros::ServerboundGamePacket; +use crate::common::movements::MoveFlags; + #[derive(Clone, Debug, AzBuf, ServerboundGamePacket)] pub struct ServerboundMovePlayerPos { pub pos: Vec3, - pub on_ground: bool, + pub flags: MoveFlags, } diff --git a/azalea-protocol/src/packets/game/s_move_player_pos_rot.rs b/azalea-protocol/src/packets/game/s_move_player_pos_rot.rs index 3460c709..feee0137 100644 --- a/azalea-protocol/src/packets/game/s_move_player_pos_rot.rs +++ b/azalea-protocol/src/packets/game/s_move_player_pos_rot.rs @@ -3,9 +3,11 @@ use azalea_core::position::Vec3; use azalea_entity::LookDirection; use azalea_protocol_macros::ServerboundGamePacket; +use crate::common::movements::MoveFlags; + #[derive(Clone, Debug, AzBuf, ServerboundGamePacket)] pub struct ServerboundMovePlayerPosRot { pub pos: Vec3, pub look_direction: LookDirection, - pub on_ground: bool, + pub flags: MoveFlags, } diff --git a/azalea-protocol/src/packets/game/s_move_player_rot.rs b/azalea-protocol/src/packets/game/s_move_player_rot.rs index 6aef91b0..d158af8c 100644 --- a/azalea-protocol/src/packets/game/s_move_player_rot.rs +++ b/azalea-protocol/src/packets/game/s_move_player_rot.rs @@ -2,8 +2,10 @@ use azalea_buf::AzBuf; use azalea_entity::LookDirection; use azalea_protocol_macros::ServerboundGamePacket; +use crate::common::movements::MoveFlags; + #[derive(Clone, Debug, AzBuf, ServerboundGamePacket)] pub struct ServerboundMovePlayerRot { pub look_direction: LookDirection, - pub on_ground: bool, + pub flags: MoveFlags, } diff --git a/azalea-protocol/src/packets/game/s_move_player_status_only.rs b/azalea-protocol/src/packets/game/s_move_player_status_only.rs index 155841f0..162b6b0e 100644 --- a/azalea-protocol/src/packets/game/s_move_player_status_only.rs +++ b/azalea-protocol/src/packets/game/s_move_player_status_only.rs @@ -1,7 +1,9 @@ use azalea_buf::AzBuf; use azalea_protocol_macros::ServerboundGamePacket; +use crate::common::movements::MoveFlags; + #[derive(Clone, Debug, AzBuf, ServerboundGamePacket)] pub struct ServerboundMovePlayerStatusOnly { - pub on_ground: bool, + pub flags: MoveFlags, }