Skip to main content

steel_protocol/packets/game/
c_player_position.rs

1//! Clientbound player position packet - sent to teleport a player.
2//!
3//! The client must respond with `SAcceptTeleportation` containing the same teleport ID.
4
5use glam::DVec3;
6use steel_macros::{ClientPacket, WriteTo};
7use steel_registry::packets::play::C_PLAYER_POSITION;
8
9/// Relative position/rotation flags.
10///
11/// When a flag is set, the corresponding value is relative to the player's current value.
12/// When not set, the value is absolute.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub struct RelativeMovement(pub i32);
15
16impl RelativeMovement {
17    /// X position is relative
18    pub const X: i32 = 1 << 0;
19    /// Y position is relative
20    pub const Y: i32 = 1 << 1;
21    /// Z position is relative
22    pub const Z: i32 = 1 << 2;
23    /// Y rotation (yaw) is relative
24    pub const Y_ROT: i32 = 1 << 3;
25    /// X rotation (pitch) is relative
26    pub const X_ROT: i32 = 1 << 4;
27    /// Delta X is relative
28    pub const DELTA_X: i32 = 1 << 5;
29    /// Delta Y is relative
30    pub const DELTA_Y: i32 = 1 << 6;
31    /// Delta Z is relative
32    pub const DELTA_Z: i32 = 1 << 7;
33    /// Rotate delta is relative
34    pub const ROTATE_DELTA: i32 = 1 << 8;
35
36    /// No relative flags (all values are absolute)
37    pub const NONE: RelativeMovement = RelativeMovement(0);
38
39    /// All rotation flags.
40    pub const ROTATION: RelativeMovement = RelativeMovement(Self::Y_ROT | Self::X_ROT);
41
42    /// Vanilla delta movement flags, including rotated-delta semantics.
43    pub const DELTA: RelativeMovement =
44        RelativeMovement(Self::DELTA_X | Self::DELTA_Y | Self::DELTA_Z | Self::ROTATE_DELTA);
45
46    /// Creates a new `RelativeMovement` with the given flags.
47    #[must_use]
48    pub const fn new(flags: i32) -> Self {
49        Self(flags)
50    }
51
52    /// Returns the union of two relative movement sets.
53    #[must_use]
54    pub const fn union(self, other: Self) -> Self {
55        Self(self.0 | other.0)
56    }
57
58    /// Returns true if the X position is relative.
59    #[must_use]
60    pub const fn is_x_relative(self) -> bool {
61        self.0 & Self::X != 0
62    }
63
64    /// Returns true if the Y position is relative.
65    #[must_use]
66    pub const fn is_y_relative(self) -> bool {
67        self.0 & Self::Y != 0
68    }
69
70    /// Returns true if the Z position is relative.
71    #[must_use]
72    pub const fn is_z_relative(self) -> bool {
73        self.0 & Self::Z != 0
74    }
75
76    /// Returns true if yaw is relative.
77    #[must_use]
78    pub const fn is_y_rot_relative(self) -> bool {
79        self.0 & Self::Y_ROT != 0
80    }
81
82    /// Returns true if pitch is relative.
83    #[must_use]
84    pub const fn is_x_rot_relative(self) -> bool {
85        self.0 & Self::X_ROT != 0
86    }
87
88    /// Returns true if delta X is relative.
89    #[must_use]
90    pub const fn is_delta_x_relative(self) -> bool {
91        self.0 & Self::DELTA_X != 0
92    }
93
94    /// Returns true if delta Y is relative.
95    #[must_use]
96    pub const fn is_delta_y_relative(self) -> bool {
97        self.0 & Self::DELTA_Y != 0
98    }
99
100    /// Returns true if delta Z is relative.
101    #[must_use]
102    pub const fn is_delta_z_relative(self) -> bool {
103        self.0 & Self::DELTA_Z != 0
104    }
105
106    /// Returns true if current delta movement is rotated by the teleport rotation delta.
107    #[must_use]
108    pub const fn rotates_delta(self) -> bool {
109        self.0 & Self::ROTATE_DELTA != 0
110    }
111}
112
113impl steel_utils::serial::WriteTo for RelativeMovement {
114    fn write(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
115        self.0.write(writer)
116    }
117}
118
119/// Sent to teleport a player to a new position.
120///
121/// The client must acknowledge this packet by sending `SAcceptTeleportation`
122/// with the same teleport ID. Until acknowledged, the server will reject
123/// position updates from the client.
124#[derive(ClientPacket, WriteTo, Clone, Debug)]
125#[packet_id(Play = C_PLAYER_POSITION)]
126pub struct CPlayerPosition {
127    /// Unique teleport ID that must be echoed back by the client.
128    #[write(as = VarInt)]
129    pub teleport_id: i32,
130    /// Target position
131    pub pos: DVec3,
132    /// Target velocity (delta movement)
133    pub vel: DVec3,
134    /// Target yaw (Y rotation)
135    pub yaw: f32,
136    /// Target pitch (X rotation)
137    pub pitch: f32,
138    /// Relative movement flags
139    pub relatives: RelativeMovement,
140}
141
142impl CPlayerPosition {
143    /// Creates a teleport packet with explicit relative flags.
144    #[must_use]
145    pub const fn new(
146        teleport_id: i32,
147        pos: DVec3,
148        vel: DVec3,
149        yaw: f32,
150        pitch: f32,
151        relatives: RelativeMovement,
152    ) -> Self {
153        Self {
154            teleport_id,
155            pos,
156            vel,
157            yaw,
158            pitch,
159            relatives,
160        }
161    }
162
163    /// Creates a new absolute teleport packet.
164    #[must_use]
165    pub const fn absolute(teleport_id: i32, pos: DVec3, yaw: f32, pitch: f32) -> Self {
166        Self::absolute_with_velocity(teleport_id, pos, DVec3::ZERO, yaw, pitch)
167    }
168
169    /// Creates a new absolute teleport packet with explicit delta movement.
170    #[must_use]
171    pub const fn absolute_with_velocity(
172        teleport_id: i32,
173        pos: DVec3,
174        vel: DVec3,
175        yaw: f32,
176        pitch: f32,
177    ) -> Self {
178        Self::new(teleport_id, pos, vel, yaw, pitch, RelativeMovement::NONE)
179    }
180
181    /// Creates a teleport packet with relative rotation (keeps current rotation).
182    #[must_use]
183    pub const fn with_relative_rotation(teleport_id: i32, pos: DVec3) -> Self {
184        Self {
185            teleport_id,
186            pos,
187            vel: DVec3::ZERO,
188            yaw: 0.0,
189            pitch: 0.0,
190            relatives: RelativeMovement::ROTATION,
191        }
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use glam::DVec3;
198
199    use super::{CPlayerPosition, RelativeMovement};
200
201    #[test]
202    fn delta_matches_vanilla_relative_delta_set() {
203        assert_eq!(
204            RelativeMovement::DELTA.0,
205            RelativeMovement::DELTA_X
206                | RelativeMovement::DELTA_Y
207                | RelativeMovement::DELTA_Z
208                | RelativeMovement::ROTATE_DELTA
209        );
210    }
211
212    #[test]
213    fn absolute_with_velocity_preserves_delta_movement() {
214        let packet = CPlayerPosition::absolute_with_velocity(
215            12,
216            DVec3::new(1.0, 2.0, 3.0),
217            DVec3::new(0.1, 0.2, 0.3),
218            45.0,
219            -10.0,
220        );
221
222        assert_eq!(packet.teleport_id, 12);
223        assert_eq!(packet.pos, DVec3::new(1.0, 2.0, 3.0));
224        assert_eq!(packet.vel, DVec3::new(0.1, 0.2, 0.3));
225        assert_eq!(packet.yaw, 45.0);
226        assert_eq!(packet.pitch, -10.0);
227        assert_eq!(packet.relatives, RelativeMovement::NONE);
228    }
229}