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    /// Counts up as teleports are made.
32    teleport_id_counter: u32,
33    /// The number of pending client teleports that have yet to receive a
34    /// confirmation. Inbound client position packets should be ignored while
35    /// this is nonzero.
36    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            // Set initial synced pos and look to NaN so a teleport always happens when first
47            // joining.
48            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/// Syncs the client's position and look with the server.
66///
67/// This should happen after chunks are loaded so the client doesn't fall though
68/// the floor.
69#[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}