diff options
| author | mat <git@matdoes.dev> | 2025-06-25 15:14:39 -1245 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2025-06-25 15:14:39 -1245 |
| commit | 08c409d04896e7057c31250f2d6f99c75b8af5b5 (patch) | |
| tree | 78c839b5d88f18df88dd1c562050f63d18814e41 | |
| parent | f9e4b65713bbacabcd54416a388a92b90f56ab47 (diff) | |
| download | azalea-drasl-08c409d04896e7057c31250f2d6f99c75b8af5b5.tar.xz | |
improve packet_order test, add BlockUpdatePlugin, fix packet order for sprinting
| -rw-r--r-- | CHANGELOG.md | 5 | ||||
| -rw-r--r-- | azalea-client/src/client.rs | 4 | ||||
| -rw-r--r-- | azalea-client/src/plugins/block_update.rs | 49 | ||||
| -rw-r--r-- | azalea-client/src/plugins/chunks.rs | 4 | ||||
| -rw-r--r-- | azalea-client/src/plugins/disconnect.rs | 3 | ||||
| -rw-r--r-- | azalea-client/src/plugins/mod.rs | 2 | ||||
| -rw-r--r-- | azalea-client/src/plugins/movement.rs | 2 | ||||
| -rw-r--r-- | azalea-client/src/plugins/packet/game/mod.rs | 36 | ||||
| -rw-r--r-- | azalea-client/tests/packet_order.rs | 91 |
9 files changed, 157 insertions, 39 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b0547f05..8e1c0931 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,17 +10,20 @@ is breaking anyways, semantic versioning is not followed. ### Added -- Update to Minecraft 1.21.6. - `HitResult` now contains the entity that's being looked at. +- A `QueuedServerBlockUpdates` component that keeps track of block updates per `Update`. ### Changed +- Update to Minecraft 1.21.6. - Renamed `azalea_entity::EntityKind` to `EntityKindComponent` to disambiguate with `azalea_registry::EntityKind`. - Moved functions and types related to hit results from `azalea::interact` to `azalea::interact::pick`. - `Client::attack` now takes `Entity` instead of `MinecraftEntityId`. ### Fixed +- Fix packet order for loading (`PlayerLoaded`/`MovePlayerPos`) and sprinting (`PlayerInput`/`PlayerCommand`). + ## [0.13.0+mc1.21.5] - 2025-06-15 ### Added diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index c9cc5259..9481ba2d 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -47,6 +47,7 @@ use uuid::Uuid; use crate::{ Account, DefaultPlugins, attack::{self}, + block_update::QueuedServerBlockUpdates, chunks::ChunkBatchInfo, connection::RawConnection, disconnect::DisconnectEvent, @@ -586,7 +587,8 @@ pub struct JoinedClientBundle { pub physics_state: PhysicsState, pub inventory: Inventory, pub tab_list: TabList, - pub current_sequence_number: BlockStatePredictionHandler, + pub block_state_prediction_handler: BlockStatePredictionHandler, + pub queued_server_block_updates: QueuedServerBlockUpdates, pub last_sent_direction: LastSentLookDirection, pub abilities: PlayerAbilities, pub permission_level: PermissionLevel, diff --git a/azalea-client/src/plugins/block_update.rs b/azalea-client/src/plugins/block_update.rs new file mode 100644 index 00000000..15e885b6 --- /dev/null +++ b/azalea-client/src/plugins/block_update.rs @@ -0,0 +1,49 @@ +use azalea_block::BlockState; +use azalea_core::position::BlockPos; +use bevy_app::{App, Plugin, Update}; +use bevy_ecs::prelude::*; + +use crate::{ + chunks::handle_receive_chunk_event, interact::BlockStatePredictionHandler, + local_player::InstanceHolder, +}; + +pub struct BlockUpdatePlugin; +impl Plugin for BlockUpdatePlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + // has to be after ReceiveChunkEvent is handled so if we get chunk+blockupdate in one + // Update then the block update actually gets applied + handle_block_update_event.after(handle_receive_chunk_event), + ); + } +} + +/// A component that holds the list of block updates that need to be handled. +/// +/// This is updated by `read_packets` (in `PreUpdate`) and handled/cleared by +/// [`handle_block_update_event`] (`Update`). +/// +/// This is a component instead of an ECS event for performance reasons. +#[derive(Component, Debug, Clone, Default)] +pub struct QueuedServerBlockUpdates { + pub list: Vec<(BlockPos, BlockState)>, +} + +pub fn handle_block_update_event( + mut query: Query<( + &mut QueuedServerBlockUpdates, + &InstanceHolder, + &mut BlockStatePredictionHandler, + )>, +) { + for (mut queued, instance_holder, mut prediction_handler) in query.iter_mut() { + let world = instance_holder.instance.read(); + for (pos, block_state) in queued.list.drain(..) { + if !prediction_handler.update_known_server_state(pos, block_state) { + world.chunks.set_block_state(pos, block_state); + } + } + } +} diff --git a/azalea-client/src/plugins/chunks.rs b/azalea-client/src/plugins/chunks.rs index 7a99c16c..a4e19f7f 100644 --- a/azalea-client/src/plugins/chunks.rs +++ b/azalea-client/src/plugins/chunks.rs @@ -28,7 +28,7 @@ impl Plugin for ChunksPlugin { Update, ( handle_chunk_batch_start_event, - handle_receive_chunk_events, + handle_receive_chunk_event, handle_chunk_batch_finished_event, ) .chain() @@ -65,7 +65,7 @@ pub struct ChunkBatchFinishedEvent { pub batch_size: u32, } -pub fn handle_receive_chunk_events( +pub fn handle_receive_chunk_event( mut events: EventReader<ReceiveChunkEvent>, mut query: Query<&InstanceHolder>, ) { diff --git a/azalea-client/src/plugins/disconnect.rs b/azalea-client/src/plugins/disconnect.rs index 8dddff09..80993476 100644 --- a/azalea-client/src/plugins/disconnect.rs +++ b/azalea-client/src/plugins/disconnect.rs @@ -56,6 +56,7 @@ pub struct DisconnectEvent { #[derive(Bundle)] pub struct RemoveOnDisconnectBundle { pub joined_client: JoinedClientBundle, + pub entity: EntityBundle, pub minecraft_entity_id: MinecraftEntityId, pub instance_holder: InstanceHolder, @@ -69,7 +70,7 @@ pub struct RemoveOnDisconnectBundle { pub chat_signing_session: chat_signing::ChatSigningSession, /// They're not authenticated anymore if they disconnected. pub is_authenticated: IsAuthenticated, - // send ServerboundPlayerLoaded next time we join + // send ServerboundPlayerLoaded next time we join. pub has_client_loaded: HasClientLoaded, } diff --git a/azalea-client/src/plugins/mod.rs b/azalea-client/src/plugins/mod.rs index 6f003b01..7c5cd3a3 100644 --- a/azalea-client/src/plugins/mod.rs +++ b/azalea-client/src/plugins/mod.rs @@ -2,6 +2,7 @@ use bevy_app::{PluginGroup, PluginGroupBuilder}; pub mod attack; pub mod auto_reconnect; +pub mod block_update; pub mod brand; pub mod chat; pub mod chat_signing; @@ -48,6 +49,7 @@ impl PluginGroup for DefaultPlugins { .add(mining::MiningPlugin) .add(attack::AttackPlugin) .add(chunks::ChunksPlugin) + .add(block_update::BlockUpdatePlugin) .add(tick_end::TickEndPlugin) .add(loading::PlayerLoadedPlugin) .add(brand::BrandPlugin) diff --git a/azalea-client/src/plugins/movement.rs b/azalea-client/src/plugins/movement.rs index 5d43261f..e97d9ec1 100644 --- a/azalea-client/src/plugins/movement.rs +++ b/azalea-client/src/plugins/movement.rs @@ -65,8 +65,8 @@ impl Plugin for MovementPlugin { .in_set(PhysicsSet) .before(ai_step) .before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing), - send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk), send_player_input_packet, + send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk), send_position.after(PhysicsSet), ) .chain(), diff --git a/azalea-client/src/plugins/packet/game/mod.rs b/azalea-client/src/plugins/packet/game/mod.rs index d9940937..fd6b712c 100644 --- a/azalea-client/src/plugins/packet/game/mod.rs +++ b/azalea-client/src/plugins/packet/game/mod.rs @@ -23,6 +23,7 @@ use tracing::{debug, error, trace, warn}; use crate::{ ClientInformation, + block_update::QueuedServerBlockUpdates, chat::{ChatPacket, ChatReceivedEvent}, chunks, connection::RawConnection, @@ -1058,17 +1059,10 @@ impl GamePacketHandler<'_> { pub fn block_update(&mut self, p: &ClientboundBlockUpdate) { debug!("Got block update packet {p:?}"); - as_system::<Query<(&InstanceHolder, &mut BlockStatePredictionHandler)>>( - self.ecs, - |mut query| { - let (local_player, mut prediction_handler) = query.get_mut(self.player).unwrap(); - - let world = local_player.instance.read(); - if !prediction_handler.update_known_server_state(p.pos, p.block_state) { - world.chunks.set_block_state(p.pos, p.block_state); - } - }, - ); + as_system::<Query<&mut QueuedServerBlockUpdates>>(self.ecs, |mut query| { + let mut queued = query.get_mut(self.player).unwrap(); + queued.list.push((p.pos, p.block_state)); + }); } pub fn animate(&mut self, p: &ClientboundAnimate) { @@ -1078,19 +1072,13 @@ impl GamePacketHandler<'_> { pub fn section_blocks_update(&mut self, p: &ClientboundSectionBlocksUpdate) { debug!("Got section blocks update packet {p:?}"); - as_system::<Query<(&InstanceHolder, &mut BlockStatePredictionHandler)>>( - self.ecs, - |mut query| { - let (local_player, mut prediction_handler) = query.get_mut(self.player).unwrap(); - let world = local_player.instance.read(); - for new_state in &p.states { - let pos = p.section_pos + new_state.pos; - if !prediction_handler.update_known_server_state(pos, new_state.state) { - world.chunks.set_block_state(pos, new_state.state); - } - } - }, - ); + as_system::<Query<&mut QueuedServerBlockUpdates>>(self.ecs, |mut query| { + let mut queued = query.get_mut(self.player).unwrap(); + for new_state in &p.states { + let pos = p.section_pos + new_state.pos; + queued.list.push((pos, new_state.state)); + } + }); } pub fn game_event(&mut self, p: &ClientboundGameEvent) { diff --git a/azalea-client/tests/packet_order.rs b/azalea-client/tests/packet_order.rs index 1d3b29a2..d4dd2558 100644 --- a/azalea-client/tests/packet_order.rs +++ b/azalea-client/tests/packet_order.rs @@ -1,26 +1,32 @@ use std::{collections::VecDeque, sync::Arc}; -use azalea_client::{packet::game::SendPacketEvent, test_utils::prelude::*}; +use azalea_client::{ + SprintDirection, StartSprintEvent, packet::game::SendPacketEvent, test_utils::prelude::*, +}; use azalea_core::{ - position::{ChunkPos, Vec3}, + position::{BlockPos, ChunkPos, Vec3}, resource_location::ResourceLocation, }; use azalea_entity::LookDirection; use azalea_protocol::{ - common::movements::{PositionMoveRotation, RelativeMovements}, + common::movements::{MoveFlags, PositionMoveRotation, RelativeMovements}, packets::{ ConnectionProtocol, config::{ClientboundFinishConfiguration, ClientboundRegistryData}, - game::{ClientboundPlayerPosition, ServerboundAcceptTeleportation, ServerboundGamePacket}, + game::{ + ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundAcceptTeleportation, + ServerboundGamePacket, ServerboundMovePlayerPos, ServerboundMovePlayerPosRot, + ServerboundMovePlayerStatusOnly, + }, }, }; -use azalea_registry::{DataRegistry, DimensionType}; +use azalea_registry::{Block, DataRegistry, DimensionType}; use bevy_ecs::observer::Trigger; use parking_lot::Mutex; use simdnbt::owned::{NbtCompound, NbtTag}; #[test] -fn test_set_health_before_login() { +fn test_packet_order() { init_tracing(); let mut simulation = Simulation::new(ConnectionProtocol::Configuration); @@ -53,16 +59,26 @@ fn test_set_health_before_login() { // 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(ClientboundBlockUpdate { + pos: BlockPos::new(1, 1, 3), + block_state: Block::Stone.into(), + }); simulation.receive_packet(ClientboundPlayerPosition { id: 1, change: PositionMoveRotation { - pos: Vec3::new(1., 2., 3.), + pos: Vec3::new(1.5, 2., 3.5), delta: Vec3::ZERO, look_direction: LookDirection::default(), }, relative: RelativeMovements::all_absolute(), }); simulation.tick(); + + assert_eq!( + simulation.get_block_state(BlockPos::new(1, 1, 3)), + Some(Block::Stone.into()) + ); + println!("sent_packets: {:?}", sent_packets.list.lock()); sent_packets.expect("AcceptTeleportation", |p| { matches!( @@ -71,7 +87,16 @@ fn test_set_health_before_login() { ) }); sent_packets.expect("MovePlayerPosRot", |p| { - matches!(p, ServerboundGamePacket::MovePlayerPosRot(_)) + matches!( + p, + ServerboundGamePacket::MovePlayerPosRot(ServerboundMovePlayerPosRot { + flags: MoveFlags { + on_ground: false, + horizontal_collision: false + }, + .. + }) + ) }); // in vanilla these might be sent in a later tick (depending on how long it @@ -81,11 +106,59 @@ fn test_set_health_before_login() { matches!(p, ServerboundGamePacket::PlayerLoaded(_)) }); sent_packets.expect("MovePlayerPos", |p| { - matches!(p, ServerboundGamePacket::MovePlayerPos(_)) + matches!( + p, + ServerboundGamePacket::MovePlayerPos(ServerboundMovePlayerPos { + flags: MoveFlags { + on_ground: false, + horizontal_collision: false + }, + .. + }) + ) }); sent_packets.expect_tick_end(); sent_packets.expect_empty(); + + // it takes a tick for on_ground to be true + simulation.tick(); + sent_packets.expect("MovePlayerStatusOnly", |p| { + matches!( + p, + ServerboundGamePacket::MovePlayerStatusOnly(ServerboundMovePlayerStatusOnly { + flags: MoveFlags { + on_ground: true, + horizontal_collision: false + } + }) + ) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // make sure nothing happens now + simulation.tick(); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); + + // now sprint for a tick + simulation.send_event(StartSprintEvent { + entity: simulation.entity, + direction: SprintDirection::Forward, + }); + simulation.tick(); + sent_packets.expect("PlayerInput", |p| { + matches!(p, ServerboundGamePacket::PlayerInput(_)) + }); + sent_packets.expect("PlayerCommand", |p| { + matches!(p, ServerboundGamePacket::PlayerCommand(_)) + }); + sent_packets.expect("MovePlayerPos", |p| { + matches!(p, ServerboundGamePacket::MovePlayerPos(_)) + }); + sent_packets.expect_tick_end(); + sent_packets.expect_empty(); } #[derive(Clone)] |
