steel_core/player/movement/teleport.rs
1//! Teleport state tracking for server-side position confirmation.
2//!
3//! When the server teleports a player, it assigns a teleport ID and waits for the
4//! client to acknowledge it via `SAcceptTeleportation`. Until acknowledged, movement
5//! packets are rejected. This struct manages that handshake.
6//!
7//! Vanilla: `ServerGamePacketListenerImpl.awaitingPositionFromClient`,
8//! `awaitingTeleport`, `awaitingTeleportTime`.
9
10use glam::DVec3;
11
12/// Tracks the state of a pending server-initiated teleport.
13pub struct TeleportState {
14 /// Position we're waiting for the client to confirm.
15 /// `Some` means we should reject movement packets until confirmed.
16 pub awaiting_position: Option<DVec3>,
17 /// Incrementing teleport ID counter (wraps at `i32::MAX`).
18 pub teleport_id: i32,
19 /// Tick count when last teleport was sent (for timeout/resend).
20 pub teleport_time: i32,
21}
22
23impl TeleportState {
24 #[must_use]
25 pub const fn new() -> Self {
26 Self {
27 awaiting_position: None,
28 teleport_id: 0,
29 teleport_time: 0,
30 }
31 }
32
33 /// Returns true if we're waiting for a teleport confirmation.
34 #[must_use]
35 pub const fn is_awaiting(&self) -> bool {
36 self.awaiting_position.is_some()
37 }
38
39 /// Advances the teleport ID, wrapping at `i32::MAX`. Returns the new ID.
40 pub const fn next_id(&mut self) -> i32 {
41 self.teleport_id = if self.teleport_id == i32::MAX {
42 0
43 } else {
44 self.teleport_id + 1
45 };
46 self.teleport_id
47 }
48
49 /// Accepts the teleport if the ID matches.
50 /// Returns the confirmed position, or `None` if the ID doesn't match.
51 pub const fn try_accept(&mut self, id: i32) -> Option<DVec3> {
52 if id == self.teleport_id {
53 self.awaiting_position.take()
54 } else {
55 None
56 }
57 }
58}