steel_registry/data_components/components/
item_colors.rs1use 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::nbt::NbtNumeric as _;
10use steel_utils::serial::{ReadFrom, WriteTo};
11
12use super::rgb_color::decode_rgb_color;
13use crate::DyeColor;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct DyedItemColor {
18 rgb: i32,
19}
20
21impl DyedItemColor {
22 pub const LEATHER_COLOR: i32 = -6_265_536;
23
24 #[must_use]
25 pub const fn new(rgb: i32) -> Self {
26 Self { rgb }
27 }
28
29 #[must_use]
30 pub const fn rgb(self) -> i32 {
31 self.rgb
32 }
33
34 #[must_use]
36 pub fn apply_dyes(current: Option<Self>, dyes: &[DyeColor]) -> Self {
37 let mut red_total = 0;
38 let mut green_total = 0;
39 let mut blue_total = 0;
40 let mut intensity_total = 0;
41 let mut color_count = 0;
42
43 if let Some(current) = current {
44 let (red, green, blue) = rgb_channels(current.rgb);
45 intensity_total += red.max(green).max(blue);
46 red_total += red;
47 green_total += green;
48 blue_total += blue;
49 color_count += 1;
50 }
51 for dye in dyes {
52 let (red, green, blue) = rgb_channels(dye.texture_diffuse_color());
53 intensity_total += red.max(green).max(blue);
54 red_total += red;
55 green_total += green;
56 blue_total += blue;
57 color_count += 1;
58 }
59
60 let mut red = red_total / color_count;
61 let mut green = green_total / color_count;
62 let mut blue = blue_total / color_count;
63 let average_intensity = intensity_total as f32 / color_count as f32;
64 let result_intensity = red.max(green).max(blue) as f32;
65 red = (red as f32 * average_intensity / result_intensity) as i32;
66 green = (green as f32 * average_intensity / result_intensity) as i32;
67 blue = (blue as f32 * average_intensity / result_intensity) as i32;
68 Self::new((red << 16) | (green << 8) | blue)
69 }
70}
71
72const fn rgb_channels(color: i32) -> (i32, i32, i32) {
73 ((color >> 16) & 255, (color >> 8) & 255, color & 255)
74}
75
76impl WriteTo for DyedItemColor {
77 fn write(&self, writer: &mut impl Write) -> Result<()> {
78 self.rgb.write(writer)
79 }
80}
81
82impl ReadFrom for DyedItemColor {
83 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
84 Ok(Self::new(i32::read(data)?))
85 }
86}
87
88impl ToNbtTag for DyedItemColor {
89 fn to_nbt_tag(self) -> NbtTag {
90 NbtTag::Int(self.rgb)
91 }
92}
93
94impl FromNbtTag for DyedItemColor {
95 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
96 decode_rgb_color(&tag.to_owned()).map(Self::new)
97 }
98}
99
100impl HashComponent for DyedItemColor {
101 fn hash_component(&self, hasher: &mut ComponentHasher) {
102 hasher.put_int(self.rgb);
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct MapItemColor {
109 rgb: i32,
110}
111
112impl MapItemColor {
113 pub const DEFAULT: Self = Self::new(4_603_950);
114
115 #[must_use]
116 pub const fn new(rgb: i32) -> Self {
117 Self { rgb }
118 }
119
120 #[must_use]
121 pub const fn rgb(self) -> i32 {
122 self.rgb
123 }
124}
125
126impl WriteTo for MapItemColor {
127 fn write(&self, writer: &mut impl Write) -> Result<()> {
128 self.rgb.write(writer)
129 }
130}
131
132impl ReadFrom for MapItemColor {
133 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
134 Ok(Self::new(i32::read(data)?))
135 }
136}
137
138impl ToNbtTag for MapItemColor {
139 fn to_nbt_tag(self) -> NbtTag {
140 NbtTag::Int(self.rgb)
141 }
142}
143
144impl FromNbtTag for MapItemColor {
145 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
146 tag.codec_i32().map(Self::new)
147 }
148}
149
150impl HashComponent for MapItemColor {
151 fn hash_component(&self, hasher: &mut ComponentHasher) {
152 hasher.put_int(self.rgb);
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub struct MapId {
159 id: i32,
160}
161
162impl MapId {
163 #[must_use]
164 pub const fn new(id: i32) -> Self {
165 Self { id }
166 }
167
168 #[must_use]
169 pub const fn id(self) -> i32 {
170 self.id
171 }
172
173 #[must_use]
174 pub fn key(self) -> String {
175 format!("maps/{}", self.id)
176 }
177}
178
179impl WriteTo for MapId {
180 fn write(&self, writer: &mut impl Write) -> Result<()> {
181 VarInt(self.id).write(writer)
182 }
183}
184
185impl ReadFrom for MapId {
186 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
187 Ok(Self::new(VarInt::read(data)?.0))
188 }
189}
190
191impl ToNbtTag for MapId {
192 fn to_nbt_tag(self) -> NbtTag {
193 NbtTag::Int(self.id)
194 }
195}
196
197impl FromNbtTag for MapId {
198 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag<'_, '_>) -> Option<Self> {
199 tag.codec_i32().map(Self::new)
200 }
201}
202
203impl HashComponent for MapId {
204 fn hash_component(&self, hasher: &mut ComponentHasher) {
205 hasher.put_int(self.id);
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use std::io::Cursor;
212
213 use simdnbt::ToNbtTag as _;
214 use simdnbt::borrow::read_tag;
215 use simdnbt::owned::{NbtList, NbtTag};
216 use steel_utils::hash::HashComponent as _;
217 use steel_utils::serial::{ReadFrom as _, WriteTo as _};
218
219 use super::{DyedItemColor, MapId, MapItemColor};
220
221 fn parse<T: simdnbt::FromNbtTag>(tag: NbtTag) -> Option<T> {
222 let mut bytes = Vec::new();
223 tag.write(&mut bytes);
224 let borrowed = read_tag(&mut Cursor::new(bytes.as_slice())).ok()?;
225 T::from_nbt_tag(borrowed.as_tag())
226 }
227
228 #[test]
229 fn dyed_color_accepts_ints_and_rgb_vectors() {
230 assert_eq!(
231 parse(NbtTag::Short(0x1234)),
232 Some(DyedItemColor::new(0x1234))
233 );
234 assert_eq!(
235 parse(NbtTag::List(NbtList::Float(vec![1.0, 0.5, 0.0]))),
236 Some(DyedItemColor::new(0xffff_7f00_u32 as i32))
237 );
238 assert_eq!(
239 parse::<DyedItemColor>(NbtTag::List(NbtList::Float(vec![1.0]))),
240 None
241 );
242 assert_eq!(
243 DyedItemColor::new(0x123456).to_nbt_tag(),
244 NbtTag::Int(0x123456)
245 );
246 }
247
248 #[test]
249 fn raw_int_network_codecs_round_trip() {
250 for value in [DyedItemColor::new(-1), DyedItemColor::new(0x123456)] {
251 let mut encoded = Vec::new();
252 value.write(&mut encoded).expect("color should encode");
253 assert_eq!(
254 DyedItemColor::read(&mut Cursor::new(encoded.as_slice()))
255 .expect("color should decode"),
256 value
257 );
258 }
259
260 let value = MapItemColor::DEFAULT;
261 let mut encoded = Vec::new();
262 value.write(&mut encoded).expect("map color should encode");
263 assert_eq!(
264 MapItemColor::read(&mut Cursor::new(encoded.as_slice()))
265 .expect("map color should decode"),
266 value
267 );
268 }
269
270 #[test]
271 fn map_id_uses_varint_network_and_int_persistence() {
272 let value = MapId::new(-17);
273 let mut encoded = Vec::new();
274 value.write(&mut encoded).expect("map ID should encode");
275 assert_eq!(
276 MapId::read(&mut Cursor::new(encoded.as_slice())).expect("map ID should decode"),
277 value
278 );
279 assert_eq!(parse(NbtTag::Long(42)), Some(MapId::new(42)));
280 assert_eq!(value.to_nbt_tag(), NbtTag::Int(-17));
281 assert_eq!(MapId::new(42).key(), "maps/42");
282 }
283
284 #[test]
285 fn persistent_hashes_use_int_codec_shape() {
286 for (actual, expected) in [
287 (
288 DyedItemColor::new(0x123456).compute_hash(),
289 NbtTag::Int(0x123456).compute_hash(),
290 ),
291 (
292 MapItemColor::DEFAULT.compute_hash(),
293 NbtTag::Int(MapItemColor::DEFAULT.rgb()).compute_hash(),
294 ),
295 (MapId::new(7).compute_hash(), NbtTag::Int(7).compute_hash()),
296 ] {
297 assert_eq!(actual, expected);
298 }
299 }
300}