Skip to main content

steel_registry/
dye_color.rs

1//! Vanilla dye colors shared by item components, entities, and blocks.
2
3use 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    /// Mirrors Vanilla's zero fallback for out-of-range network IDs.
119    #[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}