Skip to main content

steel_registry/data_components/components/
tool.rs

1//! Tool component for mining speed and drop behavior.
2
3use std::io::{Result, Write};
4use std::str::FromStr;
5
6use steel_utils::{
7    BlockStateId, Identifier,
8    hash::{ComponentHasher, HashComponent},
9    serial::{ReadFrom, WriteTo},
10};
11
12use crate::REGISTRY;
13
14/// A single rule within a Tool component.
15/// Rules are evaluated in order; the first matching rule determines the speed/drop behavior.
16#[derive(Debug, Clone, PartialEq)]
17pub struct ToolRule {
18    /// The blocks this rule applies to (can be a tag like "#minecraft:mineable/pickaxe",
19    /// a single block like "minecraft:cobweb", or a list of blocks).
20    pub blocks: Vec<Identifier>,
21    /// The mining speed for these blocks. If None, uses the tool's default_mining_speed.
22    pub speed: Option<f32>,
23    /// Whether the tool is "correct" for dropping items from these blocks.
24    /// If None, falls back to the block's requiresCorrectToolForDrops property.
25    pub correct_for_drops: Option<bool>,
26}
27
28impl ToolRule {
29    /// Creates a rule that sets both mining speed and marks the tool as correct for drops.
30    #[must_use]
31    pub fn mines_and_drops(blocks: Vec<Identifier>, speed: f32) -> Self {
32        Self {
33            blocks,
34            speed: Some(speed),
35            correct_for_drops: Some(true),
36        }
37    }
38
39    /// Creates a rule that explicitly denies drops (e.g., incorrect tool tier).
40    #[must_use]
41    pub fn denies_drops(blocks: Vec<Identifier>) -> Self {
42        Self {
43            blocks,
44            speed: None,
45            correct_for_drops: Some(false),
46        }
47    }
48
49    /// Creates a rule that only overrides the mining speed.
50    #[must_use]
51    pub fn override_speed(blocks: Vec<Identifier>, speed: f32) -> Self {
52        Self {
53            blocks,
54            speed: Some(speed),
55            correct_for_drops: None,
56        }
57    }
58
59    /// Checks if this rule matches a block state.
60    /// Handles both direct block identifiers and block tags (prefixed with #).
61    #[must_use]
62    pub fn matches_block(&self, block_state_id: BlockStateId) -> bool {
63        let Some(block) = REGISTRY.blocks.by_state_id(block_state_id) else {
64            return false;
65        };
66
67        for block_id in &self.blocks {
68            let id_str = format!("{}:{}", block_id.namespace, block_id.path);
69
70            // Check if it's a tag reference (starts with #)
71            if let Some(tag_str) = id_str.strip_prefix('#') {
72                if let Ok(tag_id) = Identifier::from_str(tag_str)
73                    && block.has_tag(&tag_id)
74                {
75                    return true;
76                }
77            } else {
78                // Direct block match
79                if block.key == *block_id {
80                    return true;
81                }
82            }
83        }
84
85        false
86    }
87}
88
89/// The tool component data - defines mining speed and drop behavior for blocks.
90#[derive(Debug, Clone, PartialEq)]
91pub struct Tool {
92    /// Rules evaluated in order to determine mining speed and drop behavior.
93    pub rules: Vec<ToolRule>,
94    /// Default mining speed when no rule matches.
95    pub default_mining_speed: f32,
96    /// Damage to apply to the item per block mined.
97    pub damage_per_block: i32,
98    /// Whether the tool can destroy blocks in creative mode.
99    pub can_destroy_blocks_in_creative: bool,
100}
101
102impl Default for Tool {
103    fn default() -> Self {
104        Self {
105            rules: Vec::new(),
106            default_mining_speed: 1.0,
107            damage_per_block: 1,
108            can_destroy_blocks_in_creative: true,
109        }
110    }
111}
112
113impl Tool {
114    /// Returns the mining speed for a block state.
115    /// Evaluates rules in order; returns the first matching rule's speed,
116    /// or `default_mining_speed` if no rule matches.
117    #[must_use]
118    pub fn get_mining_speed(&self, block_state_id: BlockStateId) -> f32 {
119        for rule in &self.rules {
120            if let Some(speed) = rule.speed
121                && rule.matches_block(block_state_id)
122            {
123                return speed;
124            }
125        }
126        self.default_mining_speed
127    }
128
129    /// Returns true if this tool is "correct" for getting drops from the block.
130    /// Evaluates rules in order; returns the first matching rule's `correct_for_drops`,
131    /// or `false` if no rule explicitly matches.
132    #[must_use]
133    pub fn is_correct_for_drops(&self, block_state_id: BlockStateId) -> bool {
134        for rule in &self.rules {
135            if let Some(correct) = rule.correct_for_drops
136                && rule.matches_block(block_state_id)
137            {
138                return correct;
139            }
140        }
141        false
142    }
143}
144
145impl WriteTo for Tool {
146    fn write(&self, _writer: &mut impl Write) -> Result<()> {
147        // TODO: Implement proper Tool serialization
148        // Format: rules (list), default_mining_speed (float), damage_per_block (VarInt)
149        Ok(())
150    }
151}
152
153impl ReadFrom for Tool {
154    fn read(_data: &mut std::io::Cursor<&[u8]>) -> Result<Self> {
155        // TODO: Implement proper Tool deserialization
156        Ok(Self::default())
157    }
158}
159
160impl HashComponent for Tool {
161    fn hash_component(&self, hasher: &mut ComponentHasher) {
162        // Tool is hashed as a map with: rules, default_mining_speed, damage_per_block
163        // For now, hash as empty map since full implementation requires proper codec
164        hasher.start_map();
165        // TODO: Add proper field hashing when Tool codec is implemented
166        hasher.end_map();
167    }
168}
169
170impl simdnbt::ToNbtTag for Tool {
171    fn to_nbt_tag(self) -> simdnbt::owned::NbtTag {
172        use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
173
174        let mut compound = NbtCompound::new();
175
176        // Serialize rules
177        let rules: Vec<NbtCompound> = self
178            .rules
179            .into_iter()
180            .map(|rule| {
181                let mut rule_compound = NbtCompound::new();
182
183                // Blocks as a list of strings
184                let blocks: Vec<NbtTag> = rule
185                    .blocks
186                    .into_iter()
187                    .map(|id| NbtTag::String(id.to_string().into()))
188                    .collect();
189                rule_compound.insert(
190                    "blocks",
191                    NbtList::String(
192                        blocks
193                            .into_iter()
194                            .filter_map(|t| match t {
195                                NbtTag::String(s) => Some(s),
196                                _ => None,
197                            })
198                            .collect(),
199                    ),
200                );
201
202                if let Some(speed) = rule.speed {
203                    rule_compound.insert("speed", speed);
204                }
205                if let Some(correct) = rule.correct_for_drops {
206                    rule_compound.insert("correct_for_drops", i8::from(correct));
207                }
208
209                rule_compound
210            })
211            .collect();
212        compound.insert("rules", NbtList::Compound(rules));
213
214        compound.insert("default_mining_speed", self.default_mining_speed);
215        compound.insert("damage_per_block", self.damage_per_block);
216        compound.insert(
217            "can_destroy_blocks_in_creative",
218            i8::from(self.can_destroy_blocks_in_creative),
219        );
220
221        NbtTag::Compound(compound)
222    }
223}
224
225impl simdnbt::FromNbtTag for Tool {
226    fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
227        let compound = tag.compound()?;
228
229        // Parse rules
230        let mut rules = Vec::new();
231        if let Some(rules_list) = compound.get("rules").and_then(|t| t.list())
232            && let Some(compounds) = rules_list.compounds()
233        {
234            for rule_compound in compounds {
235                let mut blocks = Vec::new();
236                if let Some(blocks_list) = rule_compound.get("blocks").and_then(|t| t.list())
237                    && let Some(strings) = blocks_list.strings()
238                {
239                    for s in strings {
240                        if let Ok(id) = s.to_str().parse() {
241                            blocks.push(id);
242                        }
243                    }
244                }
245
246                let speed = rule_compound.get("speed").and_then(|t| t.float());
247                let correct_for_drops = rule_compound
248                    .get("correct_for_drops")
249                    .and_then(|t| t.byte())
250                    .map(|b| b != 0);
251
252                rules.push(ToolRule {
253                    blocks,
254                    speed,
255                    correct_for_drops,
256                });
257            }
258        }
259
260        let default_mining_speed = compound
261            .get("default_mining_speed")
262            .and_then(|t| t.float())
263            .unwrap_or(1.0);
264
265        let damage_per_block = compound
266            .get("damage_per_block")
267            .and_then(|t| t.int())
268            .unwrap_or(1);
269
270        let can_destroy_blocks_in_creative = compound
271            .get("can_destroy_blocks_in_creative")
272            .and_then(|t| t.byte())
273            .map(|b| b != 0)
274            .unwrap_or(true);
275
276        Some(Self {
277            rules,
278            default_mining_speed,
279            damage_per_block,
280            can_destroy_blocks_in_creative,
281        })
282    }
283}