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:
parent
08c409d048
commit
f12589ab80
6 changed files with 157 additions and 78 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
81
azalea-client/tests/clamp_look_direction_on_teleport.rs
Normal file
81
azalea-client/tests/clamp_look_direction_on_teleport.rs
Normal 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.)
|
||||
});
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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!
|
||||
|
|
Loading…
Add table
Reference in a new issue