steel_registry/data_components/components/
use_cooldown.rs1use std::io::{Cursor, Result, Write};
4use std::str::FromStr;
5
6use simdnbt::owned::{NbtCompound, NbtTag};
7use simdnbt::{FromNbtTag, ToNbtTag};
8use steel_utils::hash::{ComponentHasher, HashComponent, HashEntry, sort_map_entries};
9use steel_utils::serial::{ReadFrom, WriteTo};
10use steel_utils::{Identifier, nbt::NbtNumeric as _};
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct UseCooldown {
15 pub seconds: f32,
16 pub cooldown_group: Option<Identifier>,
17}
18
19impl UseCooldown {
20 #[must_use]
21 pub const fn new(seconds: f32, cooldown_group: Option<Identifier>) -> Self {
22 Self {
23 seconds,
24 cooldown_group,
25 }
26 }
27
28 #[must_use]
30 pub fn ticks(&self) -> i32 {
31 (self.seconds * 20.0) as i32
32 }
33}
34
35impl WriteTo for UseCooldown {
36 fn write(&self, writer: &mut impl Write) -> Result<()> {
37 self.seconds.write(writer)?;
38 self.cooldown_group.write(writer)
39 }
40}
41
42impl ReadFrom for UseCooldown {
43 fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
44 Ok(Self {
45 seconds: f32::read(data)?,
46 cooldown_group: Option::<Identifier>::read(data)?,
47 })
48 }
49}
50
51impl ToNbtTag for UseCooldown {
52 fn to_nbt_tag(self) -> NbtTag {
53 let mut compound = NbtCompound::new();
54 compound.insert("seconds", self.seconds);
55 if let Some(group) = self.cooldown_group {
56 compound.insert("cooldown_group", group.to_string());
57 }
58 NbtTag::Compound(compound)
59 }
60}
61
62impl FromNbtTag for UseCooldown {
63 fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
64 let compound = tag.compound()?;
65 let seconds = compound.get("seconds")?.codec_f32()?;
66 if !seconds.is_finite() || seconds <= 0.0 {
67 return None;
68 }
69 let cooldown_group = match compound.get("cooldown_group") {
70 Some(tag) => Some(Identifier::from_str(&tag.string()?.to_str()).ok()?),
71 None => None,
72 };
73 Some(Self {
74 seconds,
75 cooldown_group,
76 })
77 }
78}
79
80impl HashComponent for UseCooldown {
81 fn hash_component(&self, hasher: &mut ComponentHasher) {
82 let mut entries = Vec::new();
83 push_hash_entry(&mut entries, "seconds", &self.seconds);
84 if let Some(group) = &self.cooldown_group {
85 push_hash_entry(&mut entries, "cooldown_group", &group.to_string());
86 }
87 sort_map_entries(&mut entries);
88 hasher.start_map();
89 for entry in entries {
90 hasher.put_raw_bytes(&entry.key_bytes);
91 hasher.put_raw_bytes(&entry.value_bytes);
92 }
93 hasher.end_map();
94 }
95}
96
97fn push_hash_entry<T: HashComponent + ?Sized>(entries: &mut Vec<HashEntry>, key: &str, value: &T) {
98 let mut key_hasher = ComponentHasher::new();
99 key_hasher.put_string(key);
100 let mut value_hasher = ComponentHasher::new();
101 value.hash_component(&mut value_hasher);
102 entries.push(HashEntry::new(key_hasher, value_hasher));
103}
104
105#[cfg(test)]
106mod tests {
107 use std::io::Cursor;
108
109 use simdnbt::FromNbtTag;
110 use simdnbt::borrow::{NbtTag as BorrowedNbtTag, read_tag};
111 use simdnbt::owned::{NbtCompound, NbtTag};
112 use steel_utils::Identifier;
113
114 use super::UseCooldown;
115
116 fn with_borrowed_tag<R>(tag: NbtTag, visitor: impl FnOnce(BorrowedNbtTag<'_, '_>) -> R) -> R {
117 let mut bytes = Vec::new();
118 tag.write(&mut bytes);
119 let borrowed =
120 read_tag(&mut Cursor::new(bytes.as_slice())).expect("owned test tag should parse");
121 visitor(borrowed.as_tag())
122 }
123
124 fn parse_use_cooldown(tag: NbtTag) -> Option<UseCooldown> {
125 with_borrowed_tag(tag, UseCooldown::from_nbt_tag)
126 }
127
128 #[test]
129 fn nbt_accepts_positive_seconds_and_optional_group() {
130 let mut compound = NbtCompound::new();
131 compound.insert("seconds", 5.5_f64);
132 compound.insert("cooldown_group", "minecraft:test_group");
133
134 let parsed = parse_use_cooldown(NbtTag::Compound(compound))
135 .expect("valid use_cooldown should parse");
136
137 assert_eq!(parsed.seconds, 5.5);
138 assert_eq!(
139 parsed.cooldown_group,
140 Some(Identifier::vanilla_static("test_group"))
141 );
142 }
143
144 #[test]
145 fn nbt_rejects_non_positive_seconds() {
146 for seconds in [0.0_f32, -1.0] {
147 let mut compound = NbtCompound::new();
148 compound.insert("seconds", seconds);
149
150 assert!(parse_use_cooldown(NbtTag::Compound(compound)).is_none());
151 }
152 }
153
154 #[test]
155 fn nbt_rejects_invalid_cooldown_group() {
156 let mut compound = NbtCompound::new();
157 compound.insert("seconds", 1.0_f32);
158 compound.insert("cooldown_group", "not valid");
159
160 assert!(parse_use_cooldown(NbtTag::Compound(compound)).is_none());
161 }
162}