1use std::io::{Cursor, Result, Write};
4
5use simdnbt::owned::NbtTag;
6use simdnbt::{FromNbtTag, ToNbtTag};
7use steel_utils::codec::VarInt;
8use steel_utils::hash::{ComponentHasher, HashComponent};
9use steel_utils::serial::{ReadFrom, WriteTo};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum DyeColor {
13 White,
14 Orange,
15 Magenta,
16 LightBlue,
17 Yellow,
18 Lime,
19 Pink,
20 Gray,
21 LightGray,
22 Cyan,
23 Purple,
24 Blue,
25 Brown,
26 Green,
27 Red,
28 Black,
29}
30
31impl DyeColor {
32 pub const VALUES: [Self; 16] = [
33 Self::White,
34 Self::Orange,
35 Self::Magenta,
36 Self::LightBlue,
37 Self::Yellow,
38 Self::Lime,
39 Self::Pink,
40 Self::Gray,
41 Self::LightGray,
42 Self::Cyan,
43 Self::Purple,
44 Self::Blue,
45 Self::Brown,
46 Self::Green,
47 Self::Red,
48 Self::Black,
49 ];
50
51 #[must_use]
52 pub const fn id(self) -> i32 {
53 match self {
54 Self::White => 0,
55 Self::Orange => 1,
56 Self::Magenta => 2,
57 Self::LightBlue => 3,
58 Self::Yellow => 4,
59 Self::Lime => 5,
60 Self::Pink => 6,
61 Self::Gray => 7,
62 Self::LightGray => 8,
63 Self::Cyan => 9,
64 Self::Purple => 10,
65 Self::Blue => 11,
66 Self::Brown => 12,
67 Self::Green => 13,
68 Self::Red => 14,
69 Self::Black => 15,
70 }
71 }
72
73 #[must_use]
74 pub const fn serialized_name(self) -> &'static str {
75 match self {
76 Self::White => "white",
77 Self::Orange => "orange",
78 Self::Magenta => "magenta",
79 Self::LightBlue => "light_blue",
80 Self::Yellow => "yellow",
81 Self::Lime => "lime",
82 Self::Pink => "pink",
83 Self::Gray => "gray",
84 Self::LightGray => "light_gray",
85 Self::Cyan => "cyan",
86 Self::Purple => "purple",
87 Self::Blue => "blue",
88 Self::Brown => "brown",
89 Self::Green => "green",
90 Self::Red => "red",
91 Self::Black => "black",
92 }
93 }
94
95 #[must_use]
96 pub const fn from_serialized_name(name: &str) -> Option<Self> {
97 match name {
98 "white" => Some(Self::White),
99 "orange" => Some(Self::Orange),
100 "magenta" => Some(Self::Magenta),
101 "light_blue" => Some(Self::LightBlue),
102 "yellow" => Some(Self::Yellow),
103 "lime" => Some(Self::Lime),
104 "pink" => Some(Self::Pink),
105 "gray" => Some(Self::Gray),
106 "light_gray" => Some(Self::LightGray),
107 "cyan" => Some(Self::Cyan),
108 "purple" => Some(Self::Purple),
109 "blue" => Some(Self::Blue),
110 "brown" => Some(Self::Brown),
111 "green" => Some(Self::Green),
112 "red" => Some(Self::Red),
113 "black" => Some(Self::Black),
114 _ => None,
115 }
116 }
117
118 #[must_use]
120 pub const fn by_id(id: i32) -> Self {
121 match id {
122 1 => Self::Orange,
123 2 => Self::Magenta,
124 3 => Self::LightBlue,
125 4 => Self::Yellow,
126 5 => Self::Lime,
127 6 => Self::Pink,
128 7 => Self::Gray,
129 8 => Self::LightGray,
130 9 => Self::Cyan,
131 10 => Self::Purple,
132 11 => Self::Blue,
133 12 => Self::Brown,
134 13 => Self::Green,
135 14 => Self::Red,
136 15 => Self::Black,
137 _ => Self::White,
138 }
139 }
140
141 #[must_use]
142 pub const fn texture_diffuse_color(self) -> i32 {
143 opaque(match self {
144 Self::White => 16_383_998,
145 Self::Orange => 16_351_261,
146 Self::Magenta => 13_061_821,
147 Self::LightBlue => 3_847_130,
148 Self::Yellow => 16_701_501,
149 Self::Lime => 8_439_583,
150 Self::Pink => 15_961_002,
151 Self::Gray => 4_673_362,
152 Self::LightGray => 10_329_495,
153 Self::Cyan => 1_481_884,
154 Self::Purple => 8_991_416,
155 Self::Blue => 3_949_738,
156 Self::Brown => 8_606_770,
157 Self::Green => 6_192_150,
158 Self::Red => 11_546_150,
159 Self::Black => 1_908_001,
160 })
161 }
162
163 #[must_use]
164 pub const fn firework_color(self) -> i32 {
165 match self {
166 Self::White => 15_790_320,
167 Self::Orange => 15_435_844,
168 Self::Magenta => 12_801_229,
169 Self::LightBlue => 6_719_955,
170 Self::Yellow => 14_602_026,
171 Self::Lime => 4_312_372,
172 Self::Pink => 14_188_952,
173 Self::Gray => 4_408_131,
174 Self::LightGray => 11_250_603,
175 Self::Cyan => 2_651_799,
176 Self::Purple => 8_073_150,
177 Self::Blue => 2_437_522,
178 Self::Brown => 5_320_730,
179 Self::Green => 3_887_386,
180 Self::Red => 11_743_532,
181 Self::Black => 1_973_019,
182 }
183 }
184
185 #[must_use]
186 pub const fn text_color(self) -> i32 {
187 opaque(match self {
188 Self::White => 16_777_215,
189 Self::Orange => 16_738_335,
190 Self::Magenta => 16_711_935,
191 Self::LightBlue => 10_141_901,
192 Self::Yellow => 16_776_960,
193 Self::Lime => 12_582_656,
194 Self::Pink => 16_738_740,
195 Self::Gray => 8_421_504,
196 Self::LightGray => 13_882_323,
197 Self::Cyan => 65_535,
198 Self::Purple => 10_494_192,
199 Self::Blue => 255,
200 Self::Brown => 9_127_187,
201 Self::Green => 65_280,
202 Self::Red => 16_711_680,
203 Self::Black => 0,
204 })
205 }
206
207 #[must_use]
208 pub fn by_firework_color(color: i32) -> Option<Self> {
209 Self::VALUES
210 .into_iter()
211 .find(|dye| dye.firework_color() == color)
212 }
213}
214
215const fn opaque(rgb: i32) -> i32 {
216 (0xff00_0000_u32 | (rgb as u32 & 0x00ff_ffff)) as i32
217}
218
219impl WriteTo for DyeColor {
220 fn write(&self, writer: &mut impl Write) -> Result<()> {
221 VarInt(self.id()).write(writer)
222 }
223}
224
225impl ReadFrom for DyeColor {
226 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
227 Ok(Self::by_id(VarInt::read(data)?.0))
228 }
229}
230
231impl ToNbtTag for DyeColor {
232 fn to_nbt_tag(self) -> NbtTag {
233 self.serialized_name().to_nbt_tag()
234 }
235}
236
237impl FromNbtTag for DyeColor {
238 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
239 Self::from_serialized_name(&tag.string()?.to_str())
240 }
241}
242
243impl HashComponent for DyeColor {
244 fn hash_component(&self, hasher: &mut ComponentHasher) {
245 hasher.put_string(self.serialized_name());
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use std::io::Cursor;
252
253 use simdnbt::borrow::read_tag;
254 use simdnbt::owned::NbtTag;
255 use simdnbt::{FromNbtTag as _, ToNbtTag as _};
256 use steel_utils::codec::VarInt;
257 use steel_utils::hash::HashComponent as _;
258 use steel_utils::serial::{ReadFrom as _, WriteTo as _};
259
260 use super::DyeColor;
261
262 #[test]
263 fn ids_names_and_network_fallback_match_vanilla() {
264 for (id, color) in DyeColor::VALUES.into_iter().enumerate() {
265 assert_eq!(color.id(), id as i32);
266 assert_eq!(
267 DyeColor::from_serialized_name(color.serialized_name()),
268 Some(color)
269 );
270
271 let mut encoded = Vec::new();
272 color.write(&mut encoded).expect("color should encode");
273 assert_eq!(
274 DyeColor::read(&mut Cursor::new(encoded.as_slice())).expect("color should decode"),
275 color
276 );
277 }
278
279 for id in [-1, 16, i32::MAX] {
280 let mut encoded = Vec::new();
281 VarInt(id).write(&mut encoded).expect("id should encode");
282 assert_eq!(
283 DyeColor::read(&mut Cursor::new(encoded.as_slice())).expect("id should decode"),
284 DyeColor::White
285 );
286 }
287 }
288
289 #[test]
290 fn vanilla_color_metadata_matches_representative_values() {
291 assert_eq!(
292 DyeColor::White.texture_diffuse_color(),
293 0xfff9_fffe_u32 as i32
294 );
295 assert_eq!(DyeColor::Red.firework_color(), 11_743_532);
296 assert_eq!(DyeColor::Black.text_color(), 0xff00_0000_u32 as i32);
297 assert_eq!(
298 DyeColor::by_firework_color(DyeColor::Cyan.firework_color()),
299 Some(DyeColor::Cyan)
300 );
301 }
302
303 #[test]
304 fn persistent_codec_and_hash_use_serialized_names() {
305 let value = DyeColor::LightBlue;
306 assert_eq!(value.to_nbt_tag(), NbtTag::String("light_blue".into()));
307
308 let mut bytes = Vec::new();
309 NbtTag::String("light_blue".into()).write(&mut bytes);
310 let borrowed =
311 read_tag(&mut Cursor::new(bytes.as_slice())).expect("owned color tag should parse");
312 assert_eq!(
313 DyeColor::from_nbt_tag(borrowed.as_tag()),
314 Some(DyeColor::LightBlue)
315 );
316 assert_eq!(
317 value.compute_hash(),
318 NbtTag::String("light_blue".into()).compute_hash()
319 );
320 }
321}