steel_registry/stat/
mod.rs1pub mod custom;
2mod registry;
3pub mod vanilla_stat_types;
4
5pub 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#[derive(Copy, Clone)]
25pub struct Stat {
26 stat_type_entry: StatTypeEntryRef,
27 value: &'static dyn StatValueRegistryEntry,
28}
29
30impl Stat {
31 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 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 #[must_use]
58 pub const fn stat_type(&self) -> StatTypeEntryRef {
59 self.stat_type_entry
60 }
61
62 #[must_use]
64 pub const fn stat_value(&self) -> &'static dyn StatValueRegistryEntry {
65 self.value
66 }
67
68 #[must_use]
70 pub const fn stat_type_key(&self) -> &Identifier {
71 &self.stat_type_entry.key
72 }
73
74 #[must_use]
76 pub fn stat_value_key(&self) -> &Identifier {
77 self.value.stat_value_key()
78 }
79
80 #[must_use]
82 pub fn stat_type_id(&self) -> usize {
83 self.stat_type_entry.id()
84 }
85
86 #[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 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 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 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 let mut encoded = Vec::new();
201 stat.write(&mut encoded)
202 .expect("stat should have encoded successfully");
203
204 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 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}