aboutsummaryrefslogtreecommitdiff
path: root/azalea-client
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2025-08-12 20:50:40 -1030
committermat <git@matdoes.dev>2025-08-12 20:50:40 -1030
commit55a7db13ef028f5b6c6e87a81406b3525cea196f (patch)
tree6a995bc0b46c527e9fab0874508f81e07deb673e /azalea-client
parentac66744586880afd657969ae078700a9749e293a (diff)
downloadazalea-drasl-55a7db13ef028f5b6c6e87a81406b3525cea196f.tar.xz
send correct packets on teleport
Diffstat (limited to 'azalea-client')
-rw-r--r--azalea-client/src/plugins/disconnect.rs6
-rw-r--r--azalea-client/src/plugins/loading.rs8
-rw-r--r--azalea-client/src/plugins/movement.rs12
-rw-r--r--azalea-client/src/plugins/packet/game/mod.rs21
-rw-r--r--azalea-client/src/test_utils/simulation.rs6
-rw-r--r--azalea-client/tests/packet_order.rs23
-rw-r--r--azalea-client/tests/teleport_movement.rs180
7 files changed, 215 insertions, 41 deletions
diff --git a/azalea-client/src/plugins/disconnect.rs b/azalea-client/src/plugins/disconnect.rs
index ab39ba5e..3cb9e82a 100644
--- a/azalea-client/src/plugins/disconnect.rs
+++ b/azalea-client/src/plugins/disconnect.rs
@@ -1,7 +1,9 @@
//! Disconnect a client from the server.
use azalea_chat::FormattedText;
-use azalea_entity::{EntityBundle, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle};
+use azalea_entity::{
+ EntityBundle, HasClientLoaded, InLoadedChunk, LocalEntity, metadata::PlayerMetadataBundle,
+};
use azalea_world::MinecraftEntityId;
use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::prelude::*;
@@ -10,7 +12,7 @@ use tracing::info;
use super::login::IsAuthenticated;
use crate::{
- chat_signing, client::JoinedClientBundle, connection::RawConnection, loading::HasClientLoaded,
+ chat_signing, client::JoinedClientBundle, connection::RawConnection,
local_player::InstanceHolder, tick_counter::TicksConnected,
};
diff --git a/azalea-client/src/plugins/loading.rs b/azalea-client/src/plugins/loading.rs
index 4e993a4b..86c2b2de 100644
--- a/azalea-client/src/plugins/loading.rs
+++ b/azalea-client/src/plugins/loading.rs
@@ -1,5 +1,5 @@
use azalea_core::tick::GameTick;
-use azalea_entity::{InLoadedChunk, LocalEntity};
+use azalea_entity::{HasClientLoaded, InLoadedChunk, LocalEntity, update_in_loaded_chunk};
use azalea_physics::PhysicsSet;
use azalea_protocol::packets::game::ServerboundPlayerLoaded;
use bevy_app::{App, Plugin};
@@ -12,8 +12,10 @@ impl Plugin for PlayerLoadedPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
GameTick,
+ // vanilla runs this on gameMode.tick()
player_loaded_packet
- .after(PhysicsSet)
+ .after(update_in_loaded_chunk)
+ .before(PhysicsSet)
.before(MiningSet)
.before(crate::movement::send_position),
);
@@ -27,8 +29,6 @@ impl Plugin for PlayerLoadedPlugin {
// i prefer the client one because it makes it clear that the component is only
// present on our own clients
-#[derive(Component)]
-pub struct HasClientLoaded;
#[allow(clippy::type_complexity)]
pub fn player_loaded_packet(
mut commands: Commands,
diff --git a/azalea-client/src/plugins/movement.rs b/azalea-client/src/plugins/movement.rs
index 675796f6..c9b3b070 100644
--- a/azalea-client/src/plugins/movement.rs
+++ b/azalea-client/src/plugins/movement.rs
@@ -5,7 +5,7 @@ use azalea_core::{
tick::GameTick,
};
use azalea_entity::{
- Attributes, InLoadedChunk, Jumping, LastSentPosition, LookDirection, Physics, Position,
+ Attributes, HasClientLoaded, Jumping, LastSentPosition, LookDirection, Physics, Position,
metadata::Sprinting,
};
use azalea_physics::{PhysicsSet, ai_step};
@@ -155,7 +155,7 @@ pub fn send_position(
&mut Physics,
&mut LastSentLookDirection,
),
- With<InLoadedChunk>,
+ With<HasClientLoaded>,
>,
mut commands: Commands,
) {
@@ -183,9 +183,9 @@ pub fn send_position(
// boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) >
// Mth.square(2.0E-4D) || this.positionReminder >= 20;
- let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
- > 2.0e-4f64.powi(2))
- || physics_state.position_remainder >= 20;
+ let is_delta_large_enough =
+ (x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2)) > 2.0e-4f64.powi(2);
+ let sending_position = is_delta_large_enough || physics_state.position_remainder >= 20;
let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
// if self.is_passenger() {
@@ -348,7 +348,7 @@ pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
pub fn local_player_ai_step(
mut query: Query<
(&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
- With<InLoadedChunk>,
+ With<HasClientLoaded>,
>,
) {
for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
diff --git a/azalea-client/src/plugins/packet/game/mod.rs b/azalea-client/src/plugins/packet/game/mod.rs
index fe294ad0..26d83195 100644
--- a/azalea-client/src/plugins/packet/game/mod.rs
+++ b/azalea-client/src/plugins/packet/game/mod.rs
@@ -7,8 +7,8 @@ use azalea_core::{
position::{ChunkPos, Vec3},
};
use azalea_entity::{
- Dead, EntityBundle, EntityKindComponent, LastSentPosition, LoadedBy, LocalEntity,
- LookDirection, Physics, Position, RelativeEntityUpdate,
+ Dead, EntityBundle, EntityKindComponent, HasClientLoaded, LoadedBy, LocalEntity, LookDirection,
+ Physics, Position, RelativeEntityUpdate,
indexing::{EntityIdIndex, EntityUuidIndex},
metadata::{Health, apply_metadata},
};
@@ -33,7 +33,6 @@ use crate::{
inventory::{
ClientSideCloseContainerEvent, Inventory, MenuOpenedEvent, SetContainerContentEvent,
},
- loading::HasClientLoaded,
local_player::{Hunger, InstanceHolder, LocalGameMode, PlayerAbilities, TabList},
movement::{KnockbackEvent, KnockbackType},
packet::as_system,
@@ -409,22 +408,13 @@ impl GamePacketHandler<'_> {
debug!("Got player position packet {p:?}");
as_system::<(
- Query<(
- &mut Physics,
- &mut LookDirection,
- &mut Position,
- &mut LastSentPosition,
- )>,
+ Query<(&mut Physics, &mut LookDirection, &mut Position)>,
Commands,
)>(self.ecs, |(mut query, mut commands)| {
- let Ok((mut physics, mut direction, mut position, mut last_sent_position)) =
- query.get_mut(self.player)
- else {
+ let Ok((mut physics, mut direction, mut position)) = query.get_mut(self.player) else {
return;
};
- **last_sent_position = **position;
-
p.relative
.apply(&p.change, &mut position, &mut direction, &mut physics);
// old_pos is set to the current position when we're teleported
@@ -1500,9 +1490,6 @@ impl GamePacketHandler<'_> {
physics.set_on_ground(new_on_ground);
- let mut last_sent_position =
- entity_mut.get_mut::<LastSentPosition>().unwrap();
- **last_sent_position = new_position;
let mut position = entity_mut.get_mut::<Position>().unwrap();
**position = new_position;
diff --git a/azalea-client/src/test_utils/simulation.rs b/azalea-client/src/test_utils/simulation.rs
index d898293c..a763962b 100644
--- a/azalea-client/src/test_utils/simulation.rs
+++ b/azalea-client/src/test_utils/simulation.rs
@@ -206,6 +206,12 @@ impl SentPackets {
matches!(p, ServerboundGamePacket::ClientTickEnd(_))
});
}
+ pub fn expect_pong(&self, id: u32) {
+ self.expect(
+ &format!("Ping {{ id: {id} }}"),
+ |p| matches!(p, ServerboundGamePacket::Pong(pong) if pong.id == id),
+ );
+ }
pub fn expect_empty(&self) {
let sent_packet = self.next();
if sent_packet.is_some() {
diff --git a/azalea-client/tests/packet_order.rs b/azalea-client/tests/packet_order.rs
index 014d9f46..f9e3f43b 100644
--- a/azalea-client/tests/packet_order.rs
+++ b/azalea-client/tests/packet_order.rs
@@ -11,8 +11,7 @@ use azalea_protocol::{
config::{ClientboundFinishConfiguration, ClientboundRegistryData},
game::{
ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundAcceptTeleportation,
- ServerboundGamePacket, ServerboundMovePlayerPos, ServerboundMovePlayerPosRot,
- ServerboundMovePlayerStatusOnly,
+ ServerboundGamePacket, ServerboundMovePlayerPosRot, ServerboundMovePlayerStatusOnly,
},
},
};
@@ -80,13 +79,15 @@ fn test_packet_order() {
sent_packets.expect("MovePlayerPosRot", |p| {
matches!(
p,
- ServerboundGamePacket::MovePlayerPosRot(ServerboundMovePlayerPosRot {
+ ServerboundGamePacket::MovePlayerPosRot(p)
+ if p == &ServerboundMovePlayerPosRot {
flags: MoveFlags {
on_ground: false,
horizontal_collision: false
},
- ..
- })
+ pos: Vec3::new(1.5, 2., 3.5),
+ look_direction: LookDirection::default(),
+ }
)
});
@@ -99,13 +100,11 @@ fn test_packet_order() {
sent_packets.expect("MovePlayerPos", |p| {
matches!(
p,
- ServerboundGamePacket::MovePlayerPos(ServerboundMovePlayerPos {
- flags: MoveFlags {
- on_ground: false,
- horizontal_collision: false
- },
- ..
- })
+ ServerboundGamePacket::MovePlayerPos(p)
+ if p.flags == MoveFlags {
+ on_ground: false,
+ horizontal_collision: false
+ }
)
});
diff --git a/azalea-client/tests/teleport_movement.rs b/azalea-client/tests/teleport_movement.rs
new file mode 100644
index 00000000..a03e730c
--- /dev/null
+++ b/azalea-client/tests/teleport_movement.rs
@@ -0,0 +1,180 @@
+use azalea_client::test_utils::prelude::*;
+use azalea_core::{
+ delta::PositionDelta8,
+ position::{BlockPos, ChunkPos, Vec3},
+ resource_location::ResourceLocation,
+};
+use azalea_entity::LookDirection;
+use azalea_protocol::{
+ common::movements::{MoveFlags, PositionMoveRotation, RelativeMovements},
+ packets::{
+ ConnectionProtocol,
+ config::{ClientboundFinishConfiguration, ClientboundRegistryData},
+ game::{
+ ClientboundBlockUpdate, ClientboundForgetLevelChunk, ClientboundPing,
+ ClientboundPlayerPosition, ClientboundSetChunkCacheCenter, ClientboundSetEntityMotion,
+ ServerboundGamePacket, ServerboundMovePlayerPos, ServerboundMovePlayerPosRot,
+ },
+ },
+};
+use azalea_registry::{Block, DataRegistry, DimensionType};
+use azalea_world::MinecraftEntityId;
+use simdnbt::owned::{NbtCompound, NbtTag};
+
+#[test]
+fn test_teleport_movement() {
+ 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(ClientboundSetChunkCacheCenter { x: 1, z: 23 });
+ simulation.receive_packet(make_basic_empty_chunk(
+ ChunkPos::new(1, 23),
+ (384 + 64) / 16,
+ ));
+ simulation.receive_packet(ClientboundBlockUpdate {
+ pos: BlockPos::new(31, 63, 370),
+ block_state: Block::Stone.into(),
+ });
+ simulation.receive_packet(ClientboundPlayerPosition {
+ id: 1,
+ change: PositionMoveRotation {
+ pos: Vec3::new(31.5, 64., 370.5),
+ delta: Vec3::ZERO,
+ look_direction: LookDirection::default(),
+ },
+ relative: RelativeMovements::all_absolute(),
+ });
+ simulation.tick();
+ simulation.tick();
+ sent_packets.clear();
+
+ // now teleport to a far-away location
+ tracing::info!("meow!");
+ simulation.receive_packet(ClientboundPing { id: 1 });
+ simulation.receive_packet(ClientboundPlayerPosition {
+ id: 2,
+ change: PositionMoveRotation {
+ pos: Vec3::new(10000.5, 70.0, 0.5),
+ delta: Vec3::ZERO,
+ look_direction: LookDirection::default(),
+ },
+ relative: RelativeMovements::all_absolute(),
+ });
+ simulation.receive_packet(ClientboundPing { id: 2 });
+ simulation.tick();
+ sent_packets.expect_pong(1);
+ sent_packets.expect("AcceptTeleportation", |p| {
+ matches!(p, ServerboundGamePacket::AcceptTeleportation(_))
+ });
+ sent_packets.expect("MovePlayerPosRot", |p| {
+ matches!(
+ p,
+ ServerboundGamePacket::MovePlayerPosRot(p)
+ if p == &ServerboundMovePlayerPosRot {
+ pos: Vec3::new(10000.5, 70.0, 0.5),
+ flags: MoveFlags::default(),
+ look_direction: LookDirection::default(),
+ }
+ )
+ });
+ sent_packets.expect_pong(2);
+ sent_packets.expect("MovePlayerPos", |p| {
+ matches!(
+ p,
+ ServerboundGamePacket::MovePlayerPos(p)
+ if p == &ServerboundMovePlayerPos {
+ pos: Vec3::new(10000.5, 70.0, 0.5),
+ flags: MoveFlags::default()
+ }
+ )
+ });
+
+ sent_packets.expect_tick_end();
+ sent_packets.expect_empty();
+
+ //
+
+ simulation.receive_packet(ClientboundForgetLevelChunk {
+ pos: ChunkPos { x: 1, z: 23 },
+ });
+ simulation.receive_packet(ClientboundSetChunkCacheCenter { x: 625, z: 0 });
+ simulation.receive_packet(ClientboundPing { id: 3 });
+ simulation.receive_packet(ClientboundPlayerPosition {
+ id: 3,
+ change: PositionMoveRotation {
+ pos: Vec3::new(10000.5, 70.0000001, 0.5),
+ delta: Vec3::ZERO,
+ look_direction: LookDirection::default(),
+ },
+ relative: RelativeMovements::all_absolute(),
+ });
+ simulation.receive_packet(ClientboundPing { id: 4 });
+ simulation.receive_packet(ClientboundSetEntityMotion {
+ id: MinecraftEntityId(0),
+ delta: PositionDelta8 {
+ xa: 0,
+ ya: -627,
+ za: 0,
+ },
+ });
+ simulation.receive_packet(ClientboundPing { id: 5 });
+ simulation.tick();
+
+ sent_packets.expect_pong(3);
+ sent_packets.expect("AcceptTeleportation", |p| {
+ matches!(p, ServerboundGamePacket::AcceptTeleportation(_))
+ });
+ sent_packets.expect("MovePlayerPosRot", |p| {
+ matches!(
+ p,
+ ServerboundGamePacket::MovePlayerPosRot(p)
+ if p == &ServerboundMovePlayerPosRot {
+ pos: Vec3::new(10000.5, 70.0000001, 0.5),
+ flags: MoveFlags::default(),
+ look_direction: LookDirection::default(),
+ })
+ });
+ sent_packets.expect_pong(4);
+ sent_packets.expect_pong(5);
+ sent_packets.expect("MovePlayerPos", |p| {
+ matches!(
+ p,
+ ServerboundGamePacket::MovePlayerPos(p)
+ if p == &ServerboundMovePlayerPos {
+ pos: Vec3::new(10000.5, 69.9216251, 0.5),
+ flags: MoveFlags::default()
+ }
+ )
+ });
+
+ sent_packets.expect_tick_end();
+ sent_packets.expect_empty();
+}