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

fix invalid look directions on teleport

This commit is contained in:
mat 2025-06-26 15:24:41 +09:30
parent 08c409d048
commit f12589ab80
6 changed files with 157 additions and 78 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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