aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--azalea-client/src/test_utils/simulation.rs69
-rw-r--r--azalea-client/tests/clamp_look_direction_on_teleport.rs81
-rw-r--r--azalea-client/tests/packet_order.rs72
-rw-r--r--azalea-entity/src/lib.rs4
-rw-r--r--azalea-entity/src/plugin/mod.rs8
6 files changed, 157 insertions, 78 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e1c0931..c245a34c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@ is breaking anyways, semantic versioning is not followed.
### Fixed
- Fix packet order for loading (`PlayerLoaded`/`MovePlayerPos`) and sprinting (`PlayerInput`/`PlayerCommand`).
+- Clients no longer send invalid look directions if the server teleports us with one.
## [0.13.0+mc1.21.5] - 2025-06-15
diff --git a/azalea-client/src/test_utils/simulation.rs b/azalea-client/src/test_utils/simulation.rs
index e7ac8d5b..d898293c 100644
--- a/azalea-client/src/test_utils/simulation.rs
+++ b/azalea-client/src/test_utils/simulation.rs
@@ -1,4 +1,4 @@
-use std::{fmt::Debug, sync::Arc};
+use std::{collections::VecDeque, fmt::Debug, sync::Arc};
use azalea_auth::game_profile::GameProfile;
use azalea_block::BlockState;
@@ -19,7 +19,8 @@ use azalea_protocol::{
config::{ClientboundFinishConfiguration, ClientboundRegistryData},
game::{
ClientboundAddEntity, ClientboundLevelChunkWithLight, ClientboundLogin,
- ClientboundRespawn, c_level_chunk_with_light::ClientboundLevelChunkPacketData,
+ ClientboundRespawn, ServerboundGamePacket,
+ c_level_chunk_with_light::ClientboundLevelChunkPacketData,
c_light_update::ClientboundLightUpdatePacketData,
},
},
@@ -28,13 +29,13 @@ use azalea_registry::{Biome, DimensionType, EntityKind};
use azalea_world::{Chunk, Instance, MinecraftEntityId, Section, palette::PalettedContainer};
use bevy_app::App;
use bevy_ecs::{component::Mutable, prelude::*, schedule::ExecutorKind};
-use parking_lot::RwLock;
+use parking_lot::{Mutex, RwLock};
use simdnbt::owned::{NbtCompound, NbtTag};
use uuid::Uuid;
use crate::{
InConfigState, LocalPlayerBundle, connection::RawConnection, disconnect::DisconnectEvent,
- local_player::InstanceHolder, player::GameProfileComponent,
+ local_player::InstanceHolder, packet::game::SendPacketEvent, player::GameProfileComponent,
};
/// A way to simulate a client in a server, used for some internal tests.
@@ -170,6 +171,66 @@ impl Simulation {
}
}
+#[derive(Clone)]
+pub struct SentPackets {
+ pub list: Arc<Mutex<VecDeque<ServerboundGamePacket>>>,
+}
+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<SendPacketEvent>| {
+ 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 sent_packet.is_some() {
+ 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<ServerboundGamePacket> {
+ self.list.lock().pop_front()
+ }
+}
+
#[allow(clippy::type_complexity)]
fn create_local_player_bundle(
entity: Entity,
diff --git a/azalea-client/tests/clamp_look_direction_on_teleport.rs b/azalea-client/tests/clamp_look_direction_on_teleport.rs
new file mode 100644
index 00000000..10b3215b
--- /dev/null
+++ b/azalea-client/tests/clamp_look_direction_on_teleport.rs
@@ -0,0 +1,81 @@
+use azalea_client::test_utils::prelude::*;
+use azalea_core::{
+ position::{BlockPos, ChunkPos, Vec3},
+ resource_location::ResourceLocation,
+};
+use azalea_entity::LookDirection;
+use azalea_protocol::{
+ common::movements::{PositionMoveRotation, RelativeMovements},
+ packets::{
+ ConnectionProtocol,
+ config::{ClientboundFinishConfiguration, ClientboundRegistryData},
+ game::{
+ ClientboundBlockUpdate, ClientboundPlayerPosition, ServerboundAcceptTeleportation,
+ ServerboundGamePacket,
+ },
+ },
+};
+use azalea_registry::{Block, DataRegistry, DimensionType};
+use simdnbt::owned::{NbtCompound, NbtTag};
+
+#[test]
+fn test_clamp_look_direction_on_teleport() {
+ 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(ClientboundBlockUpdate {
+ pos: BlockPos::new(1, 1, 3),
+ block_state: Block::Stone.into(),
+ });
+ simulation.receive_packet(ClientboundPlayerPosition {
+ id: 1,
+ change: PositionMoveRotation {
+ pos: Vec3::ZERO,
+ delta: Vec3::ZERO,
+ look_direction: LookDirection::new(-134.99998, 0.0),
+ },
+ relative: RelativeMovements::all_absolute(),
+ });
+ simulation.tick();
+ sent_packets.expect("AcceptTeleportation", |p| {
+ matches!(
+ p,
+ ServerboundGamePacket::AcceptTeleportation(ServerboundAcceptTeleportation { id: 1 })
+ )
+ });
+ sent_packets.expect("MovePlayerPosRot", |p| {
+ let ServerboundGamePacket::MovePlayerPosRot(p) = p else {
+ return false;
+ };
+ p.look_direction == LookDirection::new(225.00002, 0.)
+ });
+}
diff --git a/azalea-client/tests/packet_order.rs b/azalea-client/tests/packet_order.rs
index d4dd2558..014d9f46 100644
--- a/azalea-client/tests/packet_order.rs
+++ b/azalea-client/tests/packet_order.rs
@@ -1,8 +1,4 @@
-use std::{collections::VecDeque, sync::Arc};
-
-use azalea_client::{
- SprintDirection, StartSprintEvent, packet::game::SendPacketEvent, test_utils::prelude::*,
-};
+use azalea_client::{SprintDirection, StartSprintEvent, test_utils::prelude::*};
use azalea_core::{
position::{BlockPos, ChunkPos, Vec3},
resource_location::ResourceLocation,
@@ -21,8 +17,6 @@ use azalea_protocol::{
},
};
use azalea_registry::{Block, DataRegistry, DimensionType};
-use bevy_ecs::observer::Trigger;
-use parking_lot::Mutex;
use simdnbt::owned::{NbtCompound, NbtTag};
#[test]
@@ -73,13 +67,10 @@ fn test_packet_order() {
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!(
p,
@@ -160,64 +151,3 @@ fn test_packet_order() {
sent_packets.expect_tick_end();
sent_packets.expect_empty();
}
-
-#[derive(Clone)]
-pub struct SentPackets {
- list: Arc<Mutex<VecDeque<ServerboundGamePacket>>>,
-}
-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<SendPacketEvent>| {
- 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<ServerboundGamePacket> {
- self.list.lock().pop_front()
- }
-}
diff --git a/azalea-entity/src/lib.rs b/azalea-entity/src/lib.rs
index b8644546..340b6f25 100644
--- a/azalea-entity/src/lib.rs
+++ b/azalea-entity/src/lib.rs
@@ -226,8 +226,10 @@ pub struct LookDirection {
}
impl LookDirection {
+ /// Create a new look direction, while clamping and wrapping the rotations
+ /// to the allowed values.
pub fn new(y_rot: f32, x_rot: f32) -> Self {
- Self { y_rot, x_rot }
+ apply_clamp_look_direction(Self { y_rot, x_rot })
}
}
diff --git a/azalea-entity/src/plugin/mod.rs b/azalea-entity/src/plugin/mod.rs
index 65b28a59..88e87de1 100644
--- a/azalea-entity/src/plugin/mod.rs
+++ b/azalea-entity/src/plugin/mod.rs
@@ -187,10 +187,14 @@ pub struct LoadedBy(pub HashSet<Entity>);
pub fn clamp_look_direction(mut query: Query<&mut LookDirection>) {
for mut look_direction in &mut query {
- look_direction.y_rot = look_direction.y_rot.rem_euclid(360.0);
- look_direction.x_rot = look_direction.x_rot.clamp(-90.0, 90.0) % 360.0;
+ *look_direction = apply_clamp_look_direction(*look_direction);
}
}
+pub fn apply_clamp_look_direction(mut look_direction: LookDirection) -> LookDirection {
+ look_direction.y_rot = look_direction.y_rot.rem_euclid(360.0);
+ look_direction.x_rot = look_direction.x_rot.clamp(-90.0, 90.0) % 360.0;
+ look_direction
+}
/// Sets the position of the entity. This doesn't update the cache in
/// azalea-world, and should only be used within azalea-world!