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