luanti_protocol/types/
tile.rs

1use anyhow::bail;
2use luanti_protocol_derive::{LuantiDeserialize, LuantiSerialize};
3
4use crate::wire::{
5    deser::{Deserialize, DeserializeError, DeserializeResult, Deserializer},
6    ser::{Serialize, SerializeResult, Serializer},
7};
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct TileDef {
11    pub name: String,
12    pub animation: TileAnimationParams,
13    // These are stored in a single u8 flags
14    pub backface_culling: bool,
15    pub tileable_horizontal: bool,
16    pub tileable_vertical: bool,
17    // The flags also determine which of these is present
18    pub color_rgb: Option<(u8, u8, u8)>,
19    pub scale: u8,
20    pub align_style: AlignStyle,
21}
22
23const TILE_FLAG_BACKFACE_CULLING: u16 = 1 << 0;
24const TILE_FLAG_TILEABLE_HORIZONTAL: u16 = 1 << 1;
25const TILE_FLAG_TILEABLE_VERTICAL: u16 = 1 << 2;
26const TILE_FLAG_HAS_COLOR: u16 = 1 << 3;
27const TILE_FLAG_HAS_SCALE: u16 = 1 << 4;
28const TILE_FLAG_HAS_ALIGN_STYLE: u16 = 1 << 5;
29
30impl Serialize for TileDef {
31    type Input = Self;
32    fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
33        u8::serialize(&6, ser)?; // tiledef version
34        String::serialize(&value.name, ser)?;
35        TileAnimationParams::serialize(&value.animation, ser)?;
36        let mut flags: u16 = 0;
37        if value.backface_culling {
38            flags |= TILE_FLAG_BACKFACE_CULLING;
39        }
40        if value.tileable_horizontal {
41            flags |= TILE_FLAG_TILEABLE_HORIZONTAL;
42        }
43        if value.tileable_vertical {
44            flags |= TILE_FLAG_TILEABLE_VERTICAL;
45        }
46        if value.color_rgb.is_some() {
47            flags |= TILE_FLAG_HAS_COLOR;
48        }
49        if value.scale != 0 {
50            flags |= TILE_FLAG_HAS_SCALE;
51        }
52        if value.align_style != AlignStyle::Node {
53            flags |= TILE_FLAG_HAS_ALIGN_STYLE;
54        }
55        u16::serialize(&flags, ser)?;
56        if let Some(color) = &value.color_rgb {
57            u8::serialize(&color.0, ser)?;
58            u8::serialize(&color.1, ser)?;
59            u8::serialize(&color.2, ser)?;
60        }
61        if value.scale != 0 {
62            u8::serialize(&value.scale, ser)?;
63        }
64        if value.align_style != AlignStyle::Node {
65            AlignStyle::serialize(&value.align_style, ser)?;
66        }
67        Ok(())
68    }
69}
70
71impl Deserialize for TileDef {
72    type Output = Self;
73    fn deserialize(deserializer: &mut Deserializer<'_>) -> DeserializeResult<Self> {
74        let version: u8 = u8::deserialize(deserializer)?;
75        if version != 6 {
76            bail!(DeserializeError::InvalidValue(
77                "Invalid TileDef version".into(),
78            ));
79        }
80        let name = String::deserialize(deserializer)?;
81        let animation = TileAnimationParams::deserialize(deserializer)?;
82        let flags = u16::deserialize(deserializer)?;
83        #[expect(clippy::if_then_some_else_none, reason = "`?`-operator prohibits this")]
84        let color = if (flags & TILE_FLAG_HAS_COLOR) != 0 {
85            Some((
86                u8::deserialize(deserializer)?,
87                u8::deserialize(deserializer)?,
88                u8::deserialize(deserializer)?,
89            ))
90        } else {
91            None
92        };
93        let scale = if (flags & TILE_FLAG_HAS_SCALE) != 0 {
94            u8::deserialize(deserializer)?
95        } else {
96            0
97        };
98        let align_style = if (flags & TILE_FLAG_HAS_ALIGN_STYLE) != 0 {
99            AlignStyle::deserialize(deserializer)?
100        } else {
101            AlignStyle::Node
102        };
103
104        Ok(Self {
105            name,
106            animation,
107            backface_culling: (flags & TILE_FLAG_BACKFACE_CULLING) != 0,
108            tileable_horizontal: (flags & TILE_FLAG_TILEABLE_HORIZONTAL) != 0,
109            tileable_vertical: (flags & TILE_FLAG_TILEABLE_VERTICAL) != 0,
110            color_rgb: color,
111            scale,
112            align_style,
113        })
114    }
115}
116
117#[derive(Debug, Clone, PartialEq)]
118pub enum TileAnimationParams {
119    None,
120    VerticalFrames {
121        aspect_w: u16,
122        aspect_h: u16,
123        length: f32,
124    },
125    Sheet2D {
126        frames_w: u8,
127        frames_h: u8,
128        frame_length: f32,
129    },
130}
131
132// TileAnimationType
133const TAT_NONE: u8 = 0;
134const TAT_VERTICAL_FRAMES: u8 = 1;
135const TAT_SHEET_2D: u8 = 2;
136
137impl Serialize for TileAnimationParams {
138    type Input = Self;
139    fn serialize<S: Serializer>(value: &Self::Input, ser: &mut S) -> SerializeResult {
140        let typ = match value {
141            TileAnimationParams::None => TAT_NONE,
142            TileAnimationParams::VerticalFrames { .. } => TAT_VERTICAL_FRAMES,
143            TileAnimationParams::Sheet2D { .. } => TAT_SHEET_2D,
144        };
145        u8::serialize(&typ, ser)?;
146        match value {
147            TileAnimationParams::None => {}
148            TileAnimationParams::VerticalFrames {
149                aspect_w,
150                aspect_h,
151                length,
152            } => {
153                u16::serialize(aspect_w, ser)?;
154                u16::serialize(aspect_h, ser)?;
155                f32::serialize(length, ser)?;
156            }
157            TileAnimationParams::Sheet2D {
158                frames_w,
159                frames_h,
160                frame_length,
161            } => {
162                u8::serialize(frames_w, ser)?;
163                u8::serialize(frames_h, ser)?;
164                f32::serialize(frame_length, ser)?;
165            }
166        };
167        Ok(())
168    }
169}
170
171impl Deserialize for TileAnimationParams {
172    type Output = Self;
173    fn deserialize(deser: &mut Deserializer<'_>) -> DeserializeResult<Self> {
174        let typ = u8::deserialize(deser)?;
175        match typ {
176            TAT_NONE => Ok(TileAnimationParams::None),
177            TAT_VERTICAL_FRAMES => Ok(TileAnimationParams::VerticalFrames {
178                aspect_w: u16::deserialize(deser)?,
179                aspect_h: u16::deserialize(deser)?,
180                length: f32::deserialize(deser)?,
181            }),
182            TAT_SHEET_2D => Ok(TileAnimationParams::Sheet2D {
183                frames_w: u8::deserialize(deser)?,
184                frames_h: u8::deserialize(deser)?,
185                frame_length: f32::deserialize(deser)?,
186            }),
187            _ => bail!(DeserializeError::InvalidValue(format!(
188                "Invalid TileAnimationParams type {} at: {:?}",
189                typ, deser.data
190            ))),
191        }
192    }
193}
194
195#[derive(Debug, Clone, PartialEq, LuantiSerialize, LuantiDeserialize)]
196pub enum AlignStyle {
197    Node,
198    World,
199    UserDefined,
200}