valence_server/
teleport.rs
1use bevy_app::prelude::*;
2use bevy_ecs::prelude::*;
3use tracing::warn;
4use valence_entity::{Look, Position};
5use valence_math::DVec3;
6use valence_protocol::packets::play::player_position_look_s2c::PlayerPositionLookFlags;
7use valence_protocol::packets::play::{PlayerPositionLookS2c, TeleportConfirmC2s};
8use valence_protocol::WritePacket;
9
10use crate::client::{update_view_and_layers, Client, UpdateClientsSet};
11use crate::event_loop::{EventLoopPreUpdate, PacketEvent};
12use crate::spawn::update_respawn_position;
13
14pub struct TeleportPlugin;
15
16impl Plugin for TeleportPlugin {
17 fn build(&self, app: &mut App) {
18 app.add_systems(
19 PostUpdate,
20 teleport
21 .after(update_view_and_layers)
22 .before(update_respawn_position)
23 .in_set(UpdateClientsSet),
24 )
25 .add_systems(EventLoopPreUpdate, handle_teleport_confirmations);
26 }
27}
28
29#[derive(Component, Debug)]
30pub struct TeleportState {
31 teleport_id_counter: u32,
33 pending_teleports: u32,
37 pub(super) synced_pos: DVec3,
38 pub(super) synced_look: Look,
39}
40
41impl TeleportState {
42 pub(super) fn new() -> Self {
43 Self {
44 teleport_id_counter: 0,
45 pending_teleports: 0,
46 synced_pos: DVec3::NAN,
49 synced_look: Look {
50 yaw: f32::NAN,
51 pitch: f32::NAN,
52 },
53 }
54 }
55
56 pub fn teleport_id_counter(&self) -> u32 {
57 self.teleport_id_counter
58 }
59
60 pub fn pending_teleports(&self) -> u32 {
61 self.pending_teleports
62 }
63}
64
65#[allow(clippy::type_complexity)]
70fn teleport(
71 mut clients: Query<
72 (&mut Client, &mut TeleportState, &Position, &Look),
73 Or<(Changed<Position>, Changed<Look>)>,
74 >,
75) {
76 for (mut client, mut state, pos, look) in &mut clients {
77 let changed_pos = pos.0 != state.synced_pos;
78 let changed_yaw = look.yaw != state.synced_look.yaw;
79 let changed_pitch = look.pitch != state.synced_look.pitch;
80
81 if changed_pos || changed_yaw || changed_pitch {
82 state.synced_pos = pos.0;
83 state.synced_look = *look;
84
85 let flags = PlayerPositionLookFlags::new()
86 .with_x(!changed_pos)
87 .with_y(!changed_pos)
88 .with_z(!changed_pos)
89 .with_y_rot(!changed_yaw)
90 .with_x_rot(!changed_pitch);
91
92 client.write_packet(&PlayerPositionLookS2c {
93 position: if changed_pos { pos.0 } else { DVec3::ZERO },
94 yaw: if changed_yaw { look.yaw } else { 0.0 },
95 pitch: if changed_pitch { look.pitch } else { 0.0 },
96 flags,
97 teleport_id: (state.teleport_id_counter as i32).into(),
98 });
99
100 state.pending_teleports = state.pending_teleports.wrapping_add(1);
101 state.teleport_id_counter = state.teleport_id_counter.wrapping_add(1);
102 }
103 }
104}
105
106fn handle_teleport_confirmations(
107 mut packets: EventReader<PacketEvent>,
108 mut clients: Query<&mut TeleportState>,
109 mut commands: Commands,
110) {
111 for packet in packets.read() {
112 if let Some(pkt) = packet.decode::<TeleportConfirmC2s>() {
113 if let Ok(mut state) = clients.get_mut(packet.client) {
114 if state.pending_teleports == 0 {
115 warn!(
116 "unexpected teleport confirmation from client {:?}",
117 packet.client
118 );
119 commands.entity(packet.client).remove::<Client>();
120 }
121
122 let got = pkt.teleport_id.0 as u32;
123 let expected = state
124 .teleport_id_counter
125 .wrapping_sub(state.pending_teleports);
126
127 if got == expected {
128 state.pending_teleports -= 1;
129 } else {
130 warn!(
131 "unexpected teleport ID for client {:?} (expected {expected}, got {got}",
132 packet.client
133 );
134 commands.entity(packet.client).remove::<Client>();
135 }
136 }
137 }
138 }
139}