1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

improve packet_order test, add BlockUpdatePlugin, fix packet order for sprinting

This commit is contained in:
mat 2025-06-25 15:14:39 -12:45
parent f9e4b65713
commit 08c409d048
9 changed files with 157 additions and 39 deletions

View file

@ -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

View file

@ -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,

View file

@ -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);
}
}
}
}

View file

@ -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>,
) {

View file

@ -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,
}

View file

@ -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)

View file

@ -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(),

View file

@ -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) {

View file

@ -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)]