Skip to main content

steel_registry/stat/
mod.rs

1pub mod custom;
2mod registry;
3pub mod vanilla_stat_types;
4
5// Re-export some core types
6pub use registry::{
7    StatType, StatTypeEntry, StatTypeEntryRef, StatTypeRef, StatTypeRegistry, StatValueRegistry,
8    StatValueRegistryData, StatValueRegistryEntry,
9};
10
11use std::fmt::{Debug, Display, Formatter};
12use std::hash::{Hash, Hasher};
13
14use crate::{REGISTRY, RegistryEntry, RegistryExt};
15use std::io::{Cursor, Write};
16use steel_utils::Identifier;
17use steel_utils::codec::VarInt;
18use steel_utils::serial::{ReadFrom, WriteTo};
19
20/// Identifies a particular stat whose generic type is erased.
21/// This stat can also be encoded to and decoded from the network.
22///
23/// This is analogous to Vanilla's `Stat<?>`.
24#[derive(Copy, Clone)]
25pub struct Stat {
26    stat_type_entry: StatTypeEntryRef,
27    value: &'static dyn StatValueRegistryEntry,
28}
29
30impl Stat {
31    /// Attempts to create a new erased stat from its type and value with type safety.
32    /// This function panics if the stat type provided is unregistered
33    /// with the [`StatTypeRegistry`].
34    pub fn new<R: RegistryExt>(stat_type: StatTypeRef<R>, value: &'static R::Entry) -> Self
35    where
36        R::Entry: StatValueRegistryEntry,
37    {
38        let stat_type_entry = stat_type.stat_type_entry_ref();
39        Self {
40            stat_type_entry,
41            value,
42        }
43    }
44
45    /// Creates a new erased stat from its erased type and value.
46    pub const fn from_erased(
47        stat_type_entry: StatTypeEntryRef,
48        value: &'static dyn StatValueRegistryEntry,
49    ) -> Self {
50        Self {
51            stat_type_entry,
52            value,
53        }
54    }
55
56    /// Gets the type-erased stat type of this stat.
57    #[must_use]
58    pub const fn stat_type(&self) -> StatTypeEntryRef {
59        self.stat_type_entry
60    }
61
62    /// Gets the type-erased stat value of this stat.
63    #[must_use]
64    pub const fn stat_value(&self) -> &'static dyn StatValueRegistryEntry {
65        self.value
66    }
67
68    /// Gets the key of the stat type of this stat.
69    #[must_use]
70    pub const fn stat_type_key(&self) -> &Identifier {
71        &self.stat_type_entry.key
72    }
73
74    /// Gets the key of the stat value of this stat.
75    #[must_use]
76    pub fn stat_value_key(&self) -> &Identifier {
77        self.value.stat_value_key()
78    }
79
80    /// Gets the registry ID of the stat type of this stat.
81    #[must_use]
82    pub fn stat_type_id(&self) -> usize {
83        self.stat_type_entry.id()
84    }
85
86    /// Gets the registry ID of the stat value of this stat.
87    #[must_use]
88    pub fn stat_value_id(&self) -> usize {
89        self.value.stat_value_id()
90    }
91}
92
93impl Display for Stat {
94    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
95        let stat_type_identifier = self.stat_type_entry.key();
96        let value_identifier = self.value.stat_value_key();
97
98        write!(
99            f,
100            "{}.{}:{}.{}",
101            stat_type_identifier.namespace,
102            stat_type_identifier.path,
103            value_identifier.namespace,
104            value_identifier.path
105        )
106    }
107}
108
109impl Debug for Stat {
110    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
111        f.debug_tuple("StatTypeEntry")
112            .field(&self.stat_type_entry.key())
113            .field(&self.value.stat_value_key())
114            .finish()
115    }
116}
117
118impl PartialEq for Stat {
119    fn eq(&self, other: &Self) -> bool {
120        self.stat_type_entry == other.stat_type_entry
121            && self.value.stat_value_key() == other.value.stat_value_key()
122    }
123}
124
125impl Eq for Stat {}
126
127impl WriteTo for Stat {
128    fn write(&self, writer: &mut impl Write) -> std::io::Result<()> {
129        // Encode the stat type's ID, followed by the value's ID (like item ID, block ID, etc.)
130        VarInt(self.stat_type_entry.id() as i32).write(writer)?;
131        VarInt(self.value.stat_value_id() as i32).write(writer)?;
132
133        Ok(())
134    }
135}
136
137impl ReadFrom for Stat {
138    fn read(data: &mut Cursor<&[u8]>) -> std::io::Result<Self> {
139        // Decode the stat type's ID, followed by the value's ID (like item ID, block ID, etc.)
140        let stat_type_id = VarInt::read(data)?.0 as usize;
141        let stat_type_entry = REGISTRY.stat_types.by_id(stat_type_id).ok_or_else(|| {
142            std::io::Error::other(format!("Unknown stat type ID: {stat_type_id}"))
143        })?;
144
145        let read_value_id = VarInt::read(data)?.0;
146        let value_id = usize::try_from(read_value_id).map_err(|error| {
147            std::io::Error::other(format!("Invalid registry ID {read_value_id}: {error}"))
148        })?;
149
150        let value = stat_type_entry.value_from_id(value_id).ok_or_else(|| {
151            std::io::Error::other(format!(
152                "Unknown registry ID for {}: {stat_type_id}",
153                stat_type_entry.key
154            ))
155        })?;
156
157        Ok(Self {
158            stat_type_entry,
159            value,
160        })
161    }
162}
163
164impl Hash for Stat {
165    fn hash<H: Hasher>(&self, state: &mut H) {
166        self.stat_type_entry.key.hash(state);
167        self.value.stat_value_key().hash(state);
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use crate::items::ItemRegistry;
174    use crate::stat::registry::StatValueRegistry;
175    use crate::stat::{Stat, StatType, vanilla_stat_types};
176    use crate::{REGISTRY, RegistryEntry, init_vanilla_registry, vanilla_items};
177    use std::io::Cursor;
178    use steel_utils::Identifier;
179    use steel_utils::codec::VarInt;
180    use steel_utils::serial::{ReadFrom, WriteTo};
181
182    static UNREGISTERED_STAT_TYPE: StatType<ItemRegistry> =
183        StatType::new(Identifier::new_static("test", "unregistered"));
184
185    #[test]
186    fn network_encode_and_decode_stat() {
187        init_vanilla_registry();
188
189        // Test if stat creation succeeds or fails appropriately.
190        let stat = vanilla_stat_types::ITEM_USED.get(&vanilla_items::DIAMOND);
191        let should_panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
192            UNREGISTERED_STAT_TYPE.get(&vanilla_items::DIAMOND)
193        }));
194        assert!(
195            should_panic.is_err(),
196            "creating a stat with an unregistered stat type should have failed"
197        );
198
199        // Try to encode the stat.
200        let mut encoded = Vec::new();
201        stat.write(&mut encoded)
202            .expect("stat should have encoded successfully");
203
204        // Now try to decode the stat.
205        let mut reader = Cursor::new(&encoded[..]);
206        let decoded = Stat::read(&mut reader).expect("stat should have decoded successfully");
207
208        assert_eq!(decoded, stat);
209
210        // Check if we are able to decode a stat whose item ID is invalid, so it is invalid.
211        encoded.clear();
212        VarInt(vanilla_stat_types::ITEM_BROKEN.stat_type_entry_ref().id() as i32)
213            .write(&mut encoded)
214            .expect("VarInt for stat type should have encoded successfully");
215        VarInt(REGISTRY.items.len() as i32)
216            .write(&mut encoded)
217            .expect("VarInt for item ID should have encoded successfully");
218
219        let mut reader = Cursor::new(&encoded[..]);
220        assert!(
221            Stat::read(&mut reader).is_err(),
222            "stat should not have decoded successfully"
223        );
224    }
225}