Skip to main content

steel_registry/
trim_material.rs

1//! Armor trim material registry values.
2
3use std::io::{Cursor, Result as IoResult, Write};
4
5use rustc_hash::FxHashMap;
6use simdnbt::owned::{NbtCompound, NbtTag};
7use simdnbt::{FromNbtTag, ToNbtTag};
8use steel_utils::Identifier;
9use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
10use steel_utils::serial::{ReadFrom, WriteTo};
11use text_components::TextComponent;
12
13use crate::{REGISTRY, RegistryExt, RegistryHolderEntry, RegistryTags};
14
15/// Complete registry-independent trim material definition.
16///
17/// Mirrors vanilla's `TrimMaterial(Identifier paletteId, Component description)`
18/// record — the old per-equipment `asset_name`/`override_armor_assets` texture
19/// suffix system was removed in favor of a single shared palette texture.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct TrimMaterialValue {
22    palette_id: Identifier,
23    description: TextComponent,
24}
25
26impl TrimMaterialValue {
27    #[must_use]
28    pub const fn new(palette_id: Identifier, description: TextComponent) -> Self {
29        Self {
30            palette_id,
31            description,
32        }
33    }
34
35    #[must_use]
36    pub const fn palette_id(&self) -> &Identifier {
37        &self.palette_id
38    }
39
40    #[must_use]
41    pub const fn description(&self) -> &TextComponent {
42        &self.description
43    }
44
45    fn to_nbt_tag_ref(&self) -> NbtTag {
46        let mut compound = NbtCompound::new();
47        compound.insert("palette_id", self.palette_id.clone().to_nbt_tag());
48        compound.insert("description", self.description.to_codec_nbt());
49        NbtTag::Compound(compound)
50    }
51}
52
53impl WriteTo for TrimMaterialValue {
54    fn write(&self, writer: &mut impl Write) -> IoResult<()> {
55        self.palette_id.write(writer)?;
56        WriteTo::write(&self.description.to_codec_nbt(), writer)
57    }
58}
59
60impl ReadFrom for TrimMaterialValue {
61    fn read(data: &mut Cursor<&[u8]>) -> IoResult<Self> {
62        Ok(Self::new(
63            Identifier::read(data)?,
64            TextComponent::read(data)?,
65        ))
66    }
67}
68
69impl ToNbtTag for TrimMaterialValue {
70    fn to_nbt_tag(self) -> NbtTag {
71        self.to_nbt_tag_ref()
72    }
73}
74
75impl FromNbtTag for TrimMaterialValue {
76    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
77        let compound = tag.compound()?;
78        let palette_id = Identifier::from_nbt_tag(compound.get("palette_id")?)?;
79        let description = TextComponent::from_nbt(&compound.get("description")?.to_owned())?;
80        Some(Self::new(palette_id, description))
81    }
82}
83
84impl HashComponent for TrimMaterialValue {
85    fn hash_component(&self, hasher: &mut ComponentHasher) {
86        let mut entries = Vec::with_capacity(2);
87        push_hash_entry(&mut entries, "palette_id", &self.palette_id);
88        push_hash_entry(&mut entries, "description", &self.description);
89        hash_entries(entries, hasher);
90    }
91}
92
93fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
94    let mut key_hasher = ComponentHasher::new();
95    key_hasher.put_string(key);
96    let mut value_hasher = ComponentHasher::new();
97    value.hash_component(&mut value_hasher);
98    entries.push(HashEntry::new(key_hasher, value_hasher));
99}
100
101fn hash_entries(mut entries: Vec<HashEntry>, hasher: &mut ComponentHasher) {
102    sort_map_entries(&mut entries);
103    hasher.start_map();
104    for entry in &entries {
105        hasher.put_raw_bytes(&entry.key_bytes);
106        hasher.put_raw_bytes(&entry.value_bytes);
107    }
108    hasher.end_map();
109}
110
111/// Registered armor trim material definition.
112#[derive(Debug)]
113pub struct TrimMaterial {
114    pub key: Identifier,
115    value: TrimMaterialValue,
116}
117
118impl TrimMaterial {
119    #[must_use]
120    pub const fn new(key: Identifier, value: TrimMaterialValue) -> Self {
121        Self { key, value }
122    }
123
124    #[must_use]
125    pub const fn value(&self) -> &TrimMaterialValue {
126        &self.value
127    }
128}
129
130impl ToNbtTag for &TrimMaterial {
131    fn to_nbt_tag(self) -> NbtTag {
132        self.value.to_nbt_tag_ref()
133    }
134}
135
136pub type TrimMaterialRef = &'static TrimMaterial;
137
138pub struct TrimMaterialRegistry {
139    trim_materials_by_id: Vec<TrimMaterialRef>,
140    trim_materials_by_key: FxHashMap<Identifier, usize>,
141    tags: RegistryTags,
142    allows_registering: bool,
143}
144
145impl TrimMaterialRegistry {
146    #[must_use]
147    pub fn new() -> Self {
148        Self {
149            trim_materials_by_id: Vec::new(),
150            trim_materials_by_key: FxHashMap::default(),
151            tags: RegistryTags::default(),
152            allows_registering: true,
153        }
154    }
155}
156
157crate::impl_standard_methods!(
158    TrimMaterialRegistry,
159    TrimMaterialRef,
160    trim_materials_by_id,
161    trim_materials_by_key,
162    allows_registering
163);
164
165crate::impl_registry!(
166    TrimMaterialRegistry,
167    TrimMaterial,
168    trim_materials_by_id,
169    trim_materials_by_key,
170    trim_materials
171);
172crate::impl_tagged_registry!(TrimMaterialRegistry, trim_materials_by_key, "trim material");
173
174impl RegistryHolderEntry for TrimMaterial {
175    type Value = TrimMaterialValue;
176
177    const REGISTRY_NAME: &'static str = "trim material";
178
179    fn holder_value(&self) -> &Self::Value {
180        &self.value
181    }
182
183    fn holder_by_id(id: usize) -> Option<&'static Self> {
184        REGISTRY.trim_materials.by_id(id)
185    }
186
187    fn holder_by_key(key: &Identifier) -> Option<&'static Self> {
188        REGISTRY.trim_materials.by_key(key)
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use steel_utils::Identifier;
195    use text_components::format::Color;
196
197    use crate::init_vanilla_registry;
198    use crate::{REGISTRY, vanilla_trim_materials};
199
200    #[test]
201    fn generated_materials_follow_vanilla_registry_order_and_palette_ids() {
202        init_vanilla_registry();
203        let keys = REGISTRY
204            .trim_materials
205            .iter()
206            .map(|(_, material)| material.key.path.as_ref())
207            .collect::<Vec<_>>();
208        assert_eq!(
209            keys,
210            [
211                "quartz",
212                "iron",
213                "netherite",
214                "redstone",
215                "copper",
216                "gold",
217                "emerald",
218                "diamond",
219                "lapis",
220                "amethyst",
221                "resin",
222            ]
223        );
224
225        let iron = vanilla_trim_materials::IRON.value();
226        assert_eq!(iron.palette_id(), &Identifier::vanilla_static("trim/iron"));
227        assert_eq!(
228            iron.description().format.color,
229            Some(Color::Rgb(0xec, 0xec, 0xec))
230        );
231    }
232
233    #[test]
234    fn generated_definition_uses_the_flattened_persistent_shape() {
235        use simdnbt::ToNbtTag as _;
236
237        init_vanilla_registry();
238
239        let simdnbt::owned::NbtTag::Compound(iron) = (&*vanilla_trim_materials::IRON).to_nbt_tag()
240        else {
241            panic!("trim material definition should encode as a compound");
242        };
243        assert_eq!(
244            iron.string("palette_id")
245                .map(|value| value.to_str().into_owned()),
246            Some("minecraft:trim/iron".to_owned())
247        );
248        assert_eq!(
249            iron.compound("description")
250                .and_then(|description| description.string("color"))
251                .map(|value| value.to_str().into_owned()),
252            Some("#ECECEC".to_owned())
253        );
254    }
255}