Skip to main content

steel_protocol/packets/game/
c_add_entity.rs

1//! Packet sent to spawn an entity (including players) for the client.
2
3use glam::DVec3;
4use steel_macros::ClientPacket;
5use steel_registry::packets::play::C_ADD_ENTITY;
6use steel_utils::codec::{LpVec3, VarInt};
7use steel_utils::serial::WriteTo;
8use uuid::Uuid;
9
10/// Spawns an entity on the client.
11#[derive(ClientPacket, Clone, Debug)]
12#[packet_id(Play = C_ADD_ENTITY)]
13pub struct CAddEntity {
14    /// The entity ID (used for all future references to this entity)
15    pub id: i32,
16    /// The entity's UUID
17    pub uuid: Uuid,
18    /// The entity type (from registry)
19    pub entity_type: i32,
20    /// The entity position
21    pub position: DVec3,
22    /// The entity velocity (blocks per tick)
23    pub velocity: DVec3,
24    /// Pitch (vertical rotation) as angle byte
25    pub x_rot: i8,
26    /// Yaw (horizontal rotation) as angle byte
27    pub y_rot: i8,
28    /// Head yaw as angle byte
29    pub head_y_rot: i8,
30    /// Entity data value (varies by entity type)
31    pub data: i32,
32}
33
34impl WriteTo for CAddEntity {
35    fn write(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
36        VarInt(self.id).write(writer)?;
37        self.uuid.write(writer)?;
38        VarInt(self.entity_type).write(writer)?;
39        writer.write_all(&self.position.x.to_be_bytes())?;
40        writer.write_all(&self.position.y.to_be_bytes())?;
41        writer.write_all(&self.position.z.to_be_bytes())?;
42
43        // Write velocity as LpVec3
44        write_lp_vec3(writer, self.velocity)?;
45
46        self.x_rot.write(writer)?;
47        self.y_rot.write(writer)?;
48        self.head_y_rot.write(writer)?;
49        VarInt(self.data).write(writer)
50    }
51}
52
53/// Writes a velocity vector in `LpVec3` format.
54///
55/// Mirrors vanilla's `LpVec3.write()`.
56///
57/// Zero velocity is encoded as a single 0 byte.
58/// Non-zero velocity uses 6+ bytes with bit-packed components.
59pub fn write_lp_vec3(writer: &mut impl std::io::Write, velocity: DVec3) -> std::io::Result<()> {
60    LpVec3(velocity).write(writer)
61}
62
63impl CAddEntity {
64    /// Creates a new `CAddEntity` packet for spawning a player.
65    #[must_use]
66    pub const fn player(
67        id: i32,
68        uuid: Uuid,
69        entity_type_id: i32,
70        position: DVec3,
71        yaw: f32,
72        pitch: f32,
73    ) -> Self {
74        Self {
75            id,
76            uuid,
77            entity_type: entity_type_id,
78            position,
79            velocity: DVec3::ZERO,
80            x_rot: super::to_angle_byte(pitch),
81            y_rot: super::to_angle_byte(yaw),
82            head_y_rot: super::to_angle_byte(yaw),
83            data: 0,
84        }
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_zero_velocity() {
94        let mut buf = Vec::new();
95        write_lp_vec3(&mut buf, DVec3::ZERO).unwrap();
96        assert_eq!(buf, vec![0]);
97    }
98
99    #[test]
100    fn test_tiny_velocity_is_zero() {
101        let mut buf = Vec::new();
102        write_lp_vec3(&mut buf, DVec3::splat(1e-6)).unwrap();
103        assert_eq!(buf, vec![0]);
104    }
105
106    #[test]
107    fn test_non_zero_velocity() {
108        let mut buf = Vec::new();
109        write_lp_vec3(&mut buf, DVec3::ZERO.with_x(1.0)).unwrap();
110        // Non-zero velocity should be 6 bytes (no continuation needed for scale=1)
111        assert_eq!(buf.len(), 6);
112    }
113
114    #[test]
115    fn test_negative_velocity_uses_absolute_scale() {
116        let mut buf = Vec::new();
117        write_lp_vec3(&mut buf, DVec3::ZERO.with_x(-1.0)).unwrap();
118        assert_eq!(buf.len(), 6);
119    }
120
121    #[test]
122    fn test_velocity_with_scale() {
123        // Test velocity that requires scale > 3 (continuation bit)
124        let mut buf = Vec::new();
125        write_lp_vec3(&mut buf, DVec3::ZERO.with_x(5.0)).unwrap();
126        // scale=5, which is > 3, so needs continuation
127        // First byte should have continuation flag set (bit 2)
128        assert_eq!(buf[0] & 0x04, 0x04, "Continuation flag should be set");
129        // Should be 6 bytes + VarInt for scale
130        assert!(buf.len() > 6, "Should have continuation VarInt");
131    }
132}