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 pub backface_culling: bool,
15 pub tileable_horizontal: bool,
16 pub tileable_vertical: bool,
17 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)?; 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
132const 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}