luanti_protocol/types/
active_object.rs

1use super::{Array8, Array16, Pair, SColor, Wrapped32, aabb3f, s8, s16, v2f, v2s16, v3f};
2use crate::wire::{
3    deser::{Deserialize, DeserializeResult, Deserializer},
4    ser::{Serialize, SerializeResult, Serializer},
5};
6use anyhow::bail;
7use luanti_protocol_derive::{LuantiDeserialize, LuantiSerialize};
8
9/// This corresponds to `GenericCAO::Initialize` in Luanti
10#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
11pub struct GenericInitData {
12    pub version: u8,
13    pub name: String,
14    pub is_player: bool,
15    pub id: u16,
16    pub position: v3f,
17    pub rotation: v3f,
18    pub hp: u16,
19    #[wrap(Array8<Wrapped32<ActiveObjectCommand>>)]
20    pub messages: Vec<ActiveObjectCommand>,
21}
22
23// TODO(paradust): Handle this in derive macros
24#[derive(Debug, Clone, PartialEq)]
25#[expect(clippy::large_enum_variant, reason = "consider `Box`ing variants")]
26pub enum ActiveObjectCommand {
27    SetProperties(AOCSetProperties),
28    UpdatePosition(AOCUpdatePosition),
29    SetTextureMod(AOCSetTextureMod),
30    SetSprite(AOCSetSprite),
31    SetPhysicsOverride(AOCSetPhysicsOverride),
32    SetAnimation(AOCSetAnimation),
33    SetAnimationSpeed(AOCSetAnimationSpeed),
34    SetBonePosition(AOCSetBonePosition),
35    AttachTo(AOCAttachTo),
36    Punched(AOCPunched),
37    UpdateArmorGroups(AOCUpdateArmorGroups),
38    SpawnInfant(AOCSpawnInfant),
39    Obsolete1(AOCObsolete1),
40}
41
42const AO_CMD_SET_PROPERTIES: u8 = 0;
43const AO_CMD_UPDATE_POSITION: u8 = 1;
44const AO_CMD_SET_TEXTURE_MOD: u8 = 2;
45const AO_CMD_SET_SPRITE: u8 = 3;
46const AO_CMD_PUNCHED: u8 = 4;
47const AO_CMD_UPDATE_ARMOR_GROUPS: u8 = 5;
48const AO_CMD_SET_ANIMATION: u8 = 6;
49const AO_CMD_SET_BONE_POSITION: u8 = 7;
50const AO_CMD_ATTACH_TO: u8 = 8;
51const AO_CMD_SET_PHYSICS_OVERRIDE: u8 = 9;
52const AO_CMD_OBSOLETE1: u8 = 10;
53const AO_CMD_SPAWN_INFANT: u8 = 11;
54const AO_CMD_SET_ANIMATION_SPEED: u8 = 12;
55
56impl ActiveObjectCommand {
57    fn get_command_prefix(&self) -> u8 {
58        match self {
59            ActiveObjectCommand::SetProperties(_) => AO_CMD_SET_PROPERTIES,
60            ActiveObjectCommand::UpdatePosition(_) => AO_CMD_UPDATE_POSITION,
61            ActiveObjectCommand::SetTextureMod(_) => AO_CMD_SET_TEXTURE_MOD,
62            ActiveObjectCommand::SetSprite(_) => AO_CMD_SET_SPRITE,
63            ActiveObjectCommand::SetPhysicsOverride(_) => AO_CMD_SET_PHYSICS_OVERRIDE,
64            ActiveObjectCommand::SetAnimation(_) => AO_CMD_SET_ANIMATION,
65            ActiveObjectCommand::SetAnimationSpeed(_) => AO_CMD_SET_ANIMATION_SPEED,
66            ActiveObjectCommand::SetBonePosition(_) => AO_CMD_SET_BONE_POSITION,
67            ActiveObjectCommand::AttachTo(_) => AO_CMD_ATTACH_TO,
68            ActiveObjectCommand::Punched(_) => AO_CMD_PUNCHED,
69            ActiveObjectCommand::UpdateArmorGroups(_) => AO_CMD_UPDATE_ARMOR_GROUPS,
70            ActiveObjectCommand::SpawnInfant(_) => AO_CMD_SPAWN_INFANT,
71            ActiveObjectCommand::Obsolete1(_) => AO_CMD_OBSOLETE1,
72        }
73    }
74}
75
76impl Serialize for ActiveObjectCommand {
77    type Input = Self;
78    fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
79        u8::serialize(&value.get_command_prefix(), ser)?;
80        match value {
81            ActiveObjectCommand::SetProperties(command) => {
82                AOCSetProperties::serialize(command, ser)?;
83            }
84            ActiveObjectCommand::UpdatePosition(command) => {
85                AOCUpdatePosition::serialize(command, ser)?;
86            }
87            ActiveObjectCommand::SetTextureMod(command) => {
88                AOCSetTextureMod::serialize(command, ser)?;
89            }
90            ActiveObjectCommand::SetSprite(command) => AOCSetSprite::serialize(command, ser)?,
91            ActiveObjectCommand::SetPhysicsOverride(command) => {
92                AOCSetPhysicsOverride::serialize(command, ser)?;
93            }
94            ActiveObjectCommand::SetAnimation(command) => AOCSetAnimation::serialize(command, ser)?,
95            ActiveObjectCommand::SetAnimationSpeed(command) => {
96                AOCSetAnimationSpeed::serialize(command, ser)?;
97            }
98            ActiveObjectCommand::SetBonePosition(command) => {
99                AOCSetBonePosition::serialize(command, ser)?;
100            }
101            ActiveObjectCommand::AttachTo(command) => AOCAttachTo::serialize(command, ser)?,
102            ActiveObjectCommand::Punched(command) => AOCPunched::serialize(command, ser)?,
103            ActiveObjectCommand::UpdateArmorGroups(command) => {
104                AOCUpdateArmorGroups::serialize(command, ser)?;
105            }
106            ActiveObjectCommand::SpawnInfant(command) => AOCSpawnInfant::serialize(command, ser)?,
107            ActiveObjectCommand::Obsolete1(command) => AOCObsolete1::serialize(command, ser)?,
108        }
109        Ok(())
110    }
111}
112
113impl Deserialize for ActiveObjectCommand {
114    type Output = Self;
115    fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
116        #[allow(
117            clippy::enum_glob_use,
118            reason = "this improves readability and is very local"
119        )]
120        use ActiveObjectCommand::*;
121        let cmd = u8::deserialize(deser)?;
122        Ok(match cmd {
123            AO_CMD_SET_PROPERTIES => SetProperties(AOCSetProperties::deserialize(deser)?),
124            AO_CMD_UPDATE_POSITION => UpdatePosition(AOCUpdatePosition::deserialize(deser)?),
125            AO_CMD_SET_TEXTURE_MOD => SetTextureMod(AOCSetTextureMod::deserialize(deser)?),
126            AO_CMD_SET_SPRITE => SetSprite(AOCSetSprite::deserialize(deser)?),
127            AO_CMD_PUNCHED => Punched(AOCPunched::deserialize(deser)?),
128            AO_CMD_UPDATE_ARMOR_GROUPS => {
129                UpdateArmorGroups(AOCUpdateArmorGroups::deserialize(deser)?)
130            }
131            AO_CMD_SET_ANIMATION => SetAnimation(AOCSetAnimation::deserialize(deser)?),
132            AO_CMD_SET_BONE_POSITION => SetBonePosition(AOCSetBonePosition::deserialize(deser)?),
133            AO_CMD_ATTACH_TO => AttachTo(AOCAttachTo::deserialize(deser)?),
134            AO_CMD_SET_PHYSICS_OVERRIDE => {
135                SetPhysicsOverride(AOCSetPhysicsOverride::deserialize(deser)?)
136            }
137            AO_CMD_OBSOLETE1 => Obsolete1(AOCObsolete1::deserialize(deser)?),
138            AO_CMD_SPAWN_INFANT => SpawnInfant(AOCSpawnInfant::deserialize(deser)?),
139            AO_CMD_SET_ANIMATION_SPEED => {
140                SetAnimationSpeed(AOCSetAnimationSpeed::deserialize(deser)?)
141            }
142            _ => bail!("ActiveObjectCommand: Invalid cmd={}", cmd),
143        })
144    }
145}
146
147#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
148pub struct AOCSetProperties {
149    pub newprops: ObjectProperties,
150}
151
152#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
153#[expect(clippy::struct_excessive_bools, reason = "this is mandated by the API")]
154pub struct ObjectProperties {
155    pub version: u8, // must be 4
156    pub hp_max: u16,
157    pub physical: bool,
158    pub _unused: u32,
159    pub collision_box: aabb3f,
160    pub selection_box: aabb3f,
161    pub pointable: bool,
162    pub visual: String,
163    pub visual_size: v3f,
164    #[wrap(Array16<String>)]
165    pub textures: Vec<String>,
166    pub spritediv: v2s16,
167    pub initial_sprite_basepos: v2s16,
168    pub is_visible: bool,
169    pub makes_footstep_sound: bool,
170    pub automatic_rotate: f32,
171    pub mesh: String,
172    #[wrap(Array16<SColor>)]
173    pub colors: Vec<SColor>,
174    pub collide_with_objects: bool,
175    pub stepheight: f32,
176    pub automatic_face_movement_dir: bool,
177    pub automatic_face_movement_dir_offset: f32,
178    pub backface_culling: bool,
179    pub nametag: String,
180    pub nametag_color: SColor,
181    pub automatic_face_movement_max_rotation_per_sec: f32,
182    pub infotext: String,
183    pub wield_item: String,
184    pub glow: s8,
185    pub breath_max: u16,
186    pub eye_height: f32,
187    pub zoom_fov: f32,
188    pub use_texture_alpha: bool,
189    pub damage_texture_modifier: Option<String>,
190    pub shaded: Option<bool>,
191    pub show_on_minimap: Option<bool>,
192    pub nametag_bgcolor: Option<SColor>,
193    pub rotate_selectionbox: Option<bool>,
194}
195
196#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
197pub struct AOCUpdatePosition {
198    pub position: v3f,
199    pub velocity: v3f,
200    pub acceleration: v3f,
201    pub rotation: v3f,
202    pub do_interpolate: bool,
203    pub is_end_position: bool,
204    pub update_interval: f32,
205}
206
207#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
208pub struct AOCSetTextureMod {
209    pub modifier: String,
210}
211
212#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
213pub struct AOCSetSprite {
214    pub base_pos: v2s16,
215    pub anum_num_frames: u16,
216    pub anim_frame_length: f32,
217    pub select_horiz_by_yawpitch: bool,
218}
219
220#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
221pub struct AOCSetPhysicsOverride {
222    pub override_speed: f32,
223    pub override_jump: f32,
224    pub override_gravity: f32,
225    pub not_sneak: bool,
226    pub not_sneak_glitch: bool,
227    pub not_new_move: bool,
228}
229
230#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
231pub struct AOCSetAnimation {
232    pub range: v2f, // this is always casted to v2s32 by Luanti for some reason
233    pub speed: f32,
234    pub blend: f32,
235    pub no_loop: bool,
236}
237
238#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
239pub struct AOCSetAnimationSpeed {
240    pub speed: f32,
241}
242
243#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
244pub struct AOCSetBonePosition {
245    pub bone: String,
246    pub position: v3f,
247    pub rotation: v3f,
248}
249
250#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
251pub struct AOCAttachTo {
252    pub parent_id: s16,
253    pub bone: String,
254    pub position: v3f,
255    pub rotation: v3f,
256    pub force_visible: bool,
257}
258
259#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
260pub struct AOCPunched {
261    pub hp: u16,
262}
263
264#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
265pub struct AOCUpdateArmorGroups {
266    // name -> rating
267    #[wrap(Array16<Pair<String, s16>>)]
268    pub ratings: Vec<(String, s16)>,
269}
270
271#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
272pub struct AOCSpawnInfant {
273    pub child_id: u16,
274    pub typ: u8,
275}
276
277#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
278pub struct AOCObsolete1;