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/// Zero velocity is encoded as a single 0 byte.
56/// Non-zero velocity uses 6+ bytes with bit-packed components.
57pub fn write_lp_vec3(writer: &mut impl std::io::Write, velocity: DVec3) -> std::io::Result<()> {
58    LpVec3(velocity).write(writer)
59}
60
61impl CAddEntity {
62    /// Creates a new `CAddEntity` packet for spawning a player.
63    #[must_use]
64    pub const fn player(
65        id: i32,
66        uuid: Uuid,
67        entity_type_id: i32,
68        position: DVec3,
69        yaw: f32,
70        pitch: f32,
71    ) -> Self {
72        Self {
73            id,
74            uuid,
75            entity_type: entity_type_id,
76            position,
77            velocity: DVec3::ZERO,
78            x_rot: super::to_angle_byte(pitch),
79            y_rot: super::to_angle_byte(yaw),
80            head_y_rot: super::to_angle_byte(yaw),
81            data: 0,
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_zero_velocity() {
92        let mut buf = Vec::new();
93        write_lp_vec3(&mut buf, DVec3::ZERO).unwrap();
94        assert_eq!(buf, vec![0]);
95    }
96
97    #[test]
98    fn test_tiny_velocity_is_zero() {
99        let mut buf = Vec::new();
100        write_lp_vec3(&mut buf, DVec3::splat(1e-6)).unwrap();
101        assert_eq!(buf, vec![0]);
102    }
103
104    #[test]
105    fn test_non_zero_velocity() {
106        let mut buf = Vec::new();
107        write_lp_vec3(&mut buf, DVec3::ZERO.with_x(1.0)).unwrap();
108        // Non-zero velocity should be 6 bytes (no continuation needed for scale=1)
109        assert_eq!(buf.len(), 6);
110    }
111
112    #[test]
113    fn test_negative_velocity_uses_absolute_scale() {
114        let mut buf = Vec::new();
115        write_lp_vec3(&mut buf, DVec3::ZERO.with_x(-1.0)).unwrap();
116        assert_eq!(buf.len(), 6);
117    }
118
119    #[test]
120    fn test_velocity_with_scale() {
121        // Test velocity that requires scale > 3 (continuation bit)
122        let mut buf = Vec::new();
123        write_lp_vec3(&mut buf, DVec3::ZERO.with_x(5.0)).unwrap();
124        // scale=5, which is > 3, so needs continuation
125        // First byte should have continuation flag set (bit 2)
126        assert_eq!(buf[0] & 0x04, 0x04, "Continuation flag should be set");
127        // Should be 6 bytes + VarInt for scale
128        assert!(buf.len() > 6, "Should have continuation VarInt");
129    }
130}