chunkedge_server/
teleport.rs1use bevy_app::prelude::*;
2use bevy_ecs::prelude::*;
3use chunkedge_entity::{Look, Position, Velocity};
4use chunkedge_math::DVec3;
5use chunkedge_protocol::packets::play::player_position_s2c::TeleportRelativeFlags;
6use chunkedge_protocol::packets::play::{AcceptTeleportationC2s, PlayerPositionS2c};
7use chunkedge_protocol::WritePacket;
8use tracing::warn;
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_velocity: DVec3,
39 pub(super) synced_look: Look,
40}
41
42impl TeleportState {
43 pub(super) fn new() -> Self {
44 Self {
45 teleport_id_counter: 0,
46 pending_teleports: 0,
47 synced_pos: DVec3::NAN,
50 synced_velocity: DVec3::NAN,
51 synced_look: Look {
52 yaw: f32::NAN,
53 pitch: f32::NAN,
54 },
55 }
56 }
57
58 pub fn teleport_id_counter(&self) -> u32 {
59 self.teleport_id_counter
60 }
61
62 pub fn pending_teleports(&self) -> u32 {
63 self.pending_teleports
64 }
65}
66
67#[allow(clippy::type_complexity)]
72fn teleport(
73 mut clients: Query<
74 (&mut Client, &mut TeleportState, &Position, &Velocity, &Look),
75 Or<(Changed<Position>, Changed<Velocity>, Changed<Look>)>,
76 >,
77) {
78 for (mut client, mut state, pos, velocity, look) in &mut clients {
79 let changed_pos = pos.0 != state.synced_pos;
80 let changed_velocity = velocity.0 != state.synced_velocity;
81 let changed_yaw = look.yaw != state.synced_look.yaw;
82 let changed_pitch = look.pitch != state.synced_look.pitch;
83
84 if changed_pos || changed_velocity || changed_yaw || changed_pitch {
85 state.synced_pos = pos.0;
86 state.synced_velocity = velocity.0;
87 state.synced_look = *look;
88
89 let flags = TeleportRelativeFlags::new()
90 .with_x(!changed_pos)
91 .with_y(!changed_pos)
92 .with_z(!changed_pos)
93 .with_x_vel(!changed_velocity)
94 .with_y_vel(!changed_velocity)
95 .with_z_vel(!changed_velocity)
96 .with_y_rot(!changed_yaw)
97 .with_x_rot(!changed_pitch);
98
99 client.write_packet(&PlayerPositionS2c {
100 position: if changed_pos { pos.0 } else { DVec3::ZERO },
101 velocity: if changed_velocity {
102 velocity.0
103 } else {
104 DVec3::ZERO
105 },
106 yaw: if changed_yaw { look.yaw } else { 0.0 },
107 pitch: if changed_pitch { look.pitch } else { 0.0 },
108 flags,
109 teleport_id: (state.teleport_id_counter as i32).into(),
110 });
111
112 state.pending_teleports = state.pending_teleports.wrapping_add(1);
113 state.teleport_id_counter = state.teleport_id_counter.wrapping_add(1);
114 }
115 }
116}
117
118fn handle_teleport_confirmations(
119 mut packets: EventReader<PacketEvent>,
120 mut clients: Query<&mut TeleportState>,
121 mut commands: Commands,
122) {
123 for packet in packets.read() {
124 if let Some(pkt) = packet.decode::<AcceptTeleportationC2s>() {
125 if let Ok(mut state) = clients.get_mut(packet.client) {
126 if state.pending_teleports == 0 {
127 warn!(
128 "unexpected teleport confirmation from client {:?}",
129 packet.client
130 );
131 commands.entity(packet.client).remove::<Client>();
132 }
133
134 let got = pkt.teleport_id.0 as u32;
135 let expected = state
136 .teleport_id_counter
137 .wrapping_sub(state.pending_teleports);
138
139 if got == expected {
140 state.pending_teleports -= 1;
141 } else {
142 warn!(
143 "unexpected teleport ID for client {:?} (expected {expected}, got {got}",
144 packet.client
145 );
146 commands.entity(packet.client).remove::<Client>();
147 }
148 }
149 }
150 }
151}