Skip to main content

steel_registry/
item_stack.rs

1//! Item stack implementation.
2
3use std::io::{Cursor, Result, Write};
4
5use rand::RngExt;
6
7use steel_utils::{
8    Identifier,
9    codec::VarInt,
10    serial::{ReadFrom, WriteTo},
11};
12
13use crate::{
14    REGISTRY, RegistryEntry, RegistryExt,
15    data_components::{
16        Component, ComponentData, ComponentPatchEntry, DataComponentMap, DataComponentPatch,
17        DataComponentType,
18        vanilla_components::{
19            DAMAGE, ENCHANTMENTS, EQUIPPABLE, Equippable, EquippableSlot, ItemEnchantments,
20            MAX_DAMAGE, MAX_STACK_SIZE, TOOL, Tool, UNBREAKABLE,
21        },
22    },
23    items::ItemRef,
24    vanilla_items::ITEMS,
25};
26
27/// A stack of items with a count and component modifications.
28#[derive(Debug, Clone, PartialEq)]
29pub struct ItemStack {
30    /// The item type. AIR represents an empty stack.
31    pub item: ItemRef,
32    /// The number of items in this stack.
33    pub count: i32,
34    /// Modifications to the prototype components.
35    patch: DataComponentPatch,
36}
37
38impl Default for ItemStack {
39    fn default() -> Self {
40        Self::empty()
41    }
42}
43
44impl ItemStack {
45    /// Creates an empty item stack (using AIR).
46    #[must_use]
47    pub fn empty() -> Self {
48        Self {
49            item: &ITEMS.air,
50            count: 0,
51            patch: DataComponentPatch::new(),
52        }
53    }
54
55    /// Creates a new item stack with count 1.
56    #[must_use]
57    pub fn new(item: ItemRef) -> Self {
58        Self::with_count(item, 1)
59    }
60
61    /// Creates a new item stack with the specified count.
62    #[must_use]
63    pub fn with_count(item: ItemRef, count: i32) -> Self {
64        Self {
65            item,
66            count,
67            patch: DataComponentPatch::new(),
68        }
69    }
70
71    /// Creates a new item stack with the specified count and component patch.
72    #[must_use]
73    pub fn with_count_and_patch(item: ItemRef, count: i32, patch: DataComponentPatch) -> Self {
74        Self { item, count, patch }
75    }
76
77    #[must_use]
78    fn prototype(&self) -> &'static DataComponentMap {
79        &self.item.components
80    }
81
82    #[must_use]
83    pub fn is_empty(&self) -> bool {
84        self.item == &ITEMS.air || self.count <= 0
85    }
86
87    #[must_use]
88    pub fn item(&self) -> ItemRef {
89        if self.is_empty() {
90            &ITEMS.air
91        } else {
92            self.item
93        }
94    }
95
96    #[must_use]
97    pub fn count(&self) -> i32 {
98        if self.is_empty() { 0 } else { self.count }
99    }
100
101    pub fn set_count(&mut self, count: i32) {
102        self.count = count;
103    }
104
105    /// Increases the count by the given amount.
106    pub fn grow(&mut self, amount: i32) {
107        self.count += amount;
108    }
109
110    /// Decreases the count by the given amount.
111    pub fn shrink(&mut self, amount: i32) {
112        self.count -= amount;
113    }
114
115    /// Splits off the specified amount from this stack and returns it as a new stack.
116    ///
117    /// If the amount is greater than or equal to the current count, this stack becomes
118    /// empty and the entire contents are returned.
119    pub fn split(&mut self, amount: i32) -> Self {
120        let take = amount.min(self.count);
121        let result = Self {
122            item: self.item,
123            count: take,
124            patch: self.patch.clone(),
125        };
126        self.shrink(take);
127        result
128    }
129
130    /// Copies the identity (item type and patch) from another stack.
131    ///
132    /// Used when splitting stacks to preserve components.
133    #[must_use]
134    pub fn copy_with_count(&self, count: i32) -> Self {
135        if self.is_empty() {
136            Self::empty()
137        } else {
138            Self {
139                item: self.item,
140                count,
141                patch: self.patch.clone(),
142            }
143        }
144    }
145
146    /// Returns true if this item can stack (max stack size > 1 and not damaged).
147    /// Damaged items cannot stack.
148    #[must_use]
149    pub fn is_stackable(&self) -> bool {
150        self.max_stack_size() > 1 && (!self.is_damageable_item() || !self.is_damaged())
151    }
152
153    /// Returns true if this item can take damage.
154    #[must_use]
155    pub fn is_damageable_item(&self) -> bool {
156        self.has(MAX_DAMAGE) && !self.has(UNBREAKABLE) && self.has(DAMAGE)
157    }
158
159    /// Returns true if this item has taken damage.
160    #[must_use]
161    pub fn is_damaged(&self) -> bool {
162        self.is_damageable_item() && self.get_damage_value() > 0
163    }
164
165    /// Gets the current damage value of this item.
166    #[must_use]
167    pub fn get_damage_value(&self) -> i32 {
168        self.get(DAMAGE)
169            .copied()
170            .unwrap_or(0)
171            .clamp(0, self.get_max_damage())
172    }
173
174    /// Sets the damage value of this item.
175    pub fn set_damage_value(&mut self, value: i32) {
176        let clamped = value.clamp(0, self.get_max_damage());
177        self.set(DAMAGE, clamped);
178    }
179
180    /// Gets the maximum damage this item can take before breaking.
181    #[must_use]
182    pub fn get_max_damage(&self) -> i32 {
183        self.get(MAX_DAMAGE).copied().unwrap_or(0)
184    }
185
186    /// Returns true if the item is broken (damage >= max damage).
187    #[must_use]
188    pub fn is_broken(&self) -> bool {
189        self.is_damageable_item() && self.get_damage_value() >= self.get_max_damage()
190    }
191
192    /// Returns vanilla `ItemStack.nextDamageWillBreak()`.
193    #[must_use]
194    pub fn next_damage_will_break(&self) -> bool {
195        self.is_damageable_item() && self.get_damage_value() >= self.get_max_damage() - 1
196    }
197
198    /// Damages the item and breaks it if durability reaches zero.
199    ///
200    /// Returns `true` if the item broke and should be removed/replaced.
201    pub fn hurt_and_break(&mut self, amount: i32, has_infinite_materials: bool) -> bool {
202        if !self.is_damageable_item() || amount <= 0 {
203            return false;
204        }
205
206        if has_infinite_materials {
207            return false;
208        }
209
210        let unbreaking_level =
211            self.get_enchantment_level(&crate::vanilla_enchantments::UNBREAKING.key);
212        let mut effective_amount = 0;
213        for _ in 0..amount {
214            if should_consume_durability(unbreaking_level) {
215                effective_amount += 1;
216            }
217        }
218
219        if effective_amount == 0 {
220            return false;
221        }
222
223        let new_damage = self.get_damage_value() + effective_amount;
224
225        // TODO: Trigger ITEM_DURABILITY_CHANGED advancement criteria
226
227        self.set_damage_value(new_damage);
228
229        if self.is_broken() {
230            // TODO: Call onEquippedItemBroken callback which:
231            // - Broadcasts entity event (byte 47 for mainhand) for break sound/particles
232            // - Stops location-based effects (removes attribute modifiers)
233            self.shrink(1);
234            return true;
235        }
236
237        false
238    }
239
240    /// Returns true if this item has the specified component (by type).
241    #[must_use]
242    pub fn has<T: 'static>(&self, component: DataComponentType<T>) -> bool {
243        self.has_component(&component.key)
244    }
245
246    /// Returns true if this item has the specified component (by key).
247    #[must_use]
248    pub fn has_component(&self, key: &Identifier) -> bool {
249        match self.patch.get_entry(key) {
250            Some(ComponentPatchEntry::Set(_)) => true,
251            Some(ComponentPatchEntry::Removed) => false,
252            None => self.prototype().get_raw(key).is_some(),
253        }
254    }
255
256    #[must_use]
257    pub fn is_same_item(a: &Self, b: &Self) -> bool {
258        a.item().key == b.item().key
259    }
260
261    /// Checks if two stacks have the same item and components.
262    #[must_use]
263    pub fn is_same_item_same_components(a: &Self, b: &Self) -> bool {
264        if !Self::is_same_item(a, b) {
265            return false;
266        }
267        if a.is_empty() && b.is_empty() {
268            return true;
269        }
270        a.components_equal(b)
271    }
272
273    #[must_use]
274    pub fn matches(a: &Self, b: &Self) -> bool {
275        a.count() == b.count() && Self::is_same_item_same_components(a, b)
276    }
277
278    #[must_use]
279    pub fn is(&self, item: ItemRef) -> bool {
280        self.item().key == item.key
281    }
282
283    pub fn max_stack_size(&self) -> i32 {
284        self.get(MAX_STACK_SIZE).copied().unwrap_or(64)
285    }
286
287    /// Returns the equippable component if this item has one.
288    #[must_use]
289    pub fn get_equippable(&self) -> Option<&Equippable> {
290        self.get(EQUIPPABLE)
291    }
292
293    /// Returns the equipment slot this item can be equipped to, if any.
294    #[must_use]
295    pub fn get_equippable_slot(&self) -> Option<EquippableSlot> {
296        self.get_equippable().map(|e| e.slot)
297    }
298
299    /// Returns true if this item can be equipped in the given slot.
300    #[must_use]
301    pub fn is_equippable_in_slot(&self, slot: EquippableSlot) -> bool {
302        self.get_equippable_slot() == Some(slot)
303    }
304
305    /// Gets the raw component data by key.
306    #[must_use]
307    pub fn get_effective_value_raw(&self, key: &Identifier) -> Option<&ComponentData> {
308        match self.patch.get_entry(key) {
309            Some(ComponentPatchEntry::Set(data)) => Some(data),
310            Some(ComponentPatchEntry::Removed) => None,
311            None => self.prototype().get_raw(key),
312        }
313    }
314
315    /// Gets the effective value of a component, considering the patch and prototype.
316    /// Returns `None` if the component is not present or has been removed.
317    #[must_use]
318    pub fn get<T: Component>(&self, component: DataComponentType<T>) -> Option<&T> {
319        let data = self.get_effective_value_raw(&component.key)?;
320        T::from_data_ref(data)
321    }
322
323    /// Gets the effective value of a component, or returns the default value if not present.
324    #[must_use]
325    pub fn get_or_default<T: Component>(&self, component: DataComponentType<T>, default: T) -> T {
326        self.get(component).cloned().unwrap_or(default)
327    }
328
329    /// Sets a component value in this item's patch, overriding the prototype.
330    pub fn set<T: Component>(&mut self, component: DataComponentType<T>, value: T) {
331        self.patch.set(component, value);
332    }
333
334    /// Removes a component from this item (marks it as removed in the patch).
335    /// This will hide the component even if it exists in the prototype.
336    pub fn remove<T: 'static>(&mut self, component: DataComponentType<T>) {
337        self.patch.remove(component);
338    }
339
340    /// Clears any patch entry for this component (neither set nor removed).
341    /// The prototype value will be visible again.
342    pub fn clear<T: 'static>(&mut self, component: DataComponentType<T>) {
343        self.patch.clear(component);
344    }
345
346    /// Returns a reference to the component patch.
347    #[must_use]
348    pub fn patch(&self) -> &DataComponentPatch {
349        &self.patch
350    }
351
352    /// Gets the Tool component if present.
353    #[must_use]
354    pub fn get_tool(&self) -> Option<&Tool> {
355        self.get(TOOL)
356    }
357
358    /// Returns the mining speed for the given block state ID.
359    /// If no Tool component is present, returns 1.0 (hand speed).
360    #[must_use]
361    pub fn get_destroy_speed(&self, block_state_id: steel_utils::BlockStateId) -> f32 {
362        self.get_tool()
363            .map(|tool| tool.get_mining_speed(block_state_id))
364            .unwrap_or(1.0)
365    }
366
367    /// Returns true if this tool is correct for getting drops from the block.
368    #[must_use]
369    pub fn is_correct_tool_for_drops(&self, block_state_id: steel_utils::BlockStateId) -> bool {
370        self.get_tool()
371            .map(|tool| tool.is_correct_for_drops(block_state_id))
372            .unwrap_or(false)
373    }
374
375    /// Returns the damage per block for this tool (how much durability is consumed per block mined).
376    /// Returns 0 if no Tool component is present.
377    #[must_use]
378    pub fn get_tool_damage_per_block(&self) -> i32 {
379        self.get_tool()
380            .map(|tool| tool.damage_per_block)
381            .unwrap_or(0)
382    }
383
384    /// Returns true if this tool can destroy blocks in creative mode.
385    /// Returns true if no Tool component is present (default behavior).
386    #[must_use]
387    pub fn can_destroy_blocks_in_creative(&self) -> bool {
388        self.get_tool()
389            .map(|tool| tool.can_destroy_blocks_in_creative)
390            .unwrap_or(true)
391    }
392
393    #[must_use]
394    pub fn get_enchantment_level(&self, enchantment: &Identifier) -> i32 {
395        self.get_enchantments()
396            .map(|e| e.get_level(enchantment) as i32)
397            .unwrap_or(0)
398    }
399
400    #[must_use]
401    pub fn get_enchantments(&self) -> Option<&ItemEnchantments> {
402        self.get(ENCHANTMENTS)
403    }
404
405    /// Sets the damage/durability as a fraction (0.0 = broken, 1.0 = full).
406    /// If `add` is true, adds to current damage instead of setting.
407    pub fn set_damage_fraction(&mut self, _fraction: f32, _add: bool) {
408        // TODO: Implement when damage component system is ready
409        // let max_damage = self.get_max_damage();
410        // let damage_value = ((1.0 - fraction) * max_damage as f32) as i32;
411        // self.set_component(DAMAGE, damage_value);
412    }
413
414    /// Enchants this item randomly with enchantments from the given options.
415    pub fn enchant_randomly<R: rand::Rng>(
416        &mut self,
417        _options: &crate::loot_table::EnchantmentOptions,
418        _rng: &mut R,
419    ) {
420        // TODO: Implement when enchantment registry and system are ready
421        // 1. Get list of valid enchantments from options (tag or list)
422        // 2. Filter to enchantments that can apply to this item
423        // 3. Pick one randomly
424        // 4. Pick a random level for that enchantment
425        // 5. Add to ENCHANTMENTS component
426    }
427
428    /// Enchants this item as if using an enchanting table at the given level.
429    pub fn enchant_with_levels<R: rand::Rng>(
430        &mut self,
431        _level: i32,
432        _options: &crate::loot_table::EnchantmentOptions,
433        _rng: &mut R,
434    ) {
435        // TODO: Implement when enchantment registry and system are ready
436        // This simulates the enchanting table algorithm:
437        // 1. Calculate modified level based on item enchantability
438        // 2. Generate list of possible enchantments for that level
439        // 3. Filter by options (tag or list)
440        // 4. Apply enchantments with proper weights
441    }
442
443    /// Copies components from a source (block entity, attacker, etc.) to this item.
444    pub fn copy_components<R: rand::Rng>(
445        &mut self,
446        _source: crate::loot_table::CopySource,
447        _include: &[Identifier],
448        _ctx: &crate::loot_table::LootContext<'_, R>,
449    ) {
450        // TODO: Implement when block entity system is ready
451        // 1. Get the source entity/block entity from context
452        // 2. For each component in `include`, copy it to this item's patch
453    }
454
455    /// Copies block state properties to this item (for blocks like note_block).
456    pub fn copy_block_state<R: rand::Rng>(
457        &mut self,
458        _block: &Identifier,
459        _properties: &[&str],
460        _ctx: &crate::loot_table::LootContext<'_, R>,
461    ) {
462        // TODO: Implement block state copying
463        // 1. Get block state from context
464        // 2. For each property, store it in the item's BLOCK_STATE component
465    }
466
467    /// Sets components from a JSON string representation.
468    pub fn set_components_from_json(&mut self, _components: &str) {
469        // TODO: Implement component parsing from JSON
470        // Parse the JSON and set each component in the patch
471    }
472
473    /// Sets custom NBT data on this item (merges with existing custom_data).
474    pub fn set_custom_data(&mut self, _tag: &str) {
475        // TODO: Implement when NBT/SNBT parsing is available
476        // 1. Parse the tag string as SNBT (Stringified NBT)
477        // 2. Merge it with existing CUSTOM_DATA component
478        // 3. Set the merged result as the new CUSTOM_DATA
479    }
480
481    /// Applies furnace smelting to convert this item (e.g., raw iron -> iron ingot).
482    pub fn apply_furnace_smelt(&mut self) {
483        // TODO: Implement smelting recipe lookup
484        // 1. Look up this item in smelting recipes
485        // 2. If found, replace self.item with the result item
486        // Note: This changes the item type, not just components
487    }
488
489    /// Creates an exploration map pointing to a structure.
490    pub fn create_exploration_map(
491        &mut self,
492        _destination: &Identifier,
493        _decoration: &Identifier,
494        _zoom: i32,
495        _skip_existing_chunks: bool,
496    ) {
497        // TODO: Implement exploration map creation
498        // 1. Change item to filled_map
499        // 2. Set MAP_DECORATIONS component
500        // 3. Set destination structure tag
501        // This requires world access to find the structure
502    }
503
504    /// Sets the custom name or item name of this item.
505    pub fn set_name(&mut self, _name: &str, _target: crate::loot_table::NameTarget) {
506        // TODO: Implement name setting
507        // Parse the name as a text component and set CUSTOM_NAME or ITEM_NAME
508    }
509
510    /// Sets the ominous bottle amplifier.
511    pub fn set_ominous_bottle_amplifier(&mut self, _amplifier: i32) {
512        // TODO: Implement ominous bottle amplifier component
513        // Set the OMINOUS_BOTTLE_AMPLIFIER component
514    }
515
516    /// Sets the potion type for this item.
517    pub fn set_potion(&mut self, _id: &Identifier) {
518        // TODO: Implement potion type setting
519        // Set the POTION_CONTENTS component with the potion ID
520    }
521
522    /// Sets the suspicious stew effects for this item.
523    pub fn set_stew_effects<R: rand::Rng>(
524        &mut self,
525        _effects: &[crate::loot_table::StewEffect],
526        _rng: &mut R,
527    ) {
528        // TODO: Implement stew effect setting
529        // Set the SUSPICIOUS_STEW_EFFECTS component
530        // Duration is determined by each effect's NumberProvider
531    }
532
533    /// Sets the instrument for a goat horn.
534    pub fn set_instrument<R: rand::Rng>(&mut self, _options: &Identifier, _rng: &mut R) {
535        // TODO: Implement instrument setting
536        // Pick a random instrument from the tag and set INSTRUMENT component
537    }
538
539    pub fn set_enchantments(&mut self, enchantments: &[(Identifier, u32)], add: bool) {
540        let mut current = self
541            .get(ENCHANTMENTS)
542            .cloned()
543            .unwrap_or_else(ItemEnchantments::empty);
544
545        for (key, level) in enchantments {
546            if add {
547                let existing = current.get_level(key);
548                current.set(key.clone(), existing + *level);
549            } else {
550                current.set(key.clone(), *level);
551            }
552        }
553
554        self.set(ENCHANTMENTS, current);
555    }
556
557    /// Vanilla `ItemStack.enchant` → `Mutable.upgrade`: keeps the higher of existing vs new level.
558    pub fn upgrade_enchantment(&mut self, enchantment: Identifier, level: u32) {
559        let mut current = self
560            .get(ENCHANTMENTS)
561            .cloned()
562            .unwrap_or_else(ItemEnchantments::empty);
563        current.upgrade(enchantment, level);
564        self.set(ENCHANTMENTS, current);
565    }
566
567    /// Changes the item type entirely.
568    pub fn set_item(&mut self, new_item: &Identifier) {
569        if let Some(item_ref) = REGISTRY.items.by_key(new_item) {
570            self.item = item_ref;
571            // Note: Components patch may need adjustment for new item type
572        }
573    }
574
575    /// Copies the name from a source entity/block to this item.
576    pub fn copy_name<R: rand::Rng>(
577        &mut self,
578        _source: crate::loot_table::CopySource,
579        _ctx: &crate::loot_table::LootContext<'_, R>,
580    ) {
581        // TODO: Implement when entity/block entity name access is available
582        // Get name from source (block_entity.custom_name or entity.custom_name)
583        // Set as CUSTOM_NAME component
584    }
585
586    /// Sets lore lines on this item.
587    pub fn set_lore(&mut self, _lore: &[&str], _mode: crate::loot_table::ListOperation) {
588        // TODO: Implement lore setting
589        // Parse lore strings as text components and set LORE component
590        // Apply mode (replace, append, insert, etc.)
591    }
592
593    /// Sets container inventory contents.
594    pub fn set_contents<R: rand::Rng>(
595        &mut self,
596        _entries: &[crate::loot_table::LootEntry],
597        _component_type: &Identifier,
598        _ctx: &mut crate::loot_table::LootContext<'_, R>,
599    ) {
600        // TODO: Implement container contents setting
601        // Generate items from entries and set as CONTAINER component
602    }
603
604    /// Modifies existing container contents.
605    pub fn modify_contents<R: rand::Rng>(
606        &mut self,
607        _modifier: &[crate::loot_table::ConditionalLootFunction],
608        _component_type: &Identifier,
609        _ctx: &mut crate::loot_table::LootContext<'_, R>,
610    ) {
611        // TODO: Implement container contents modification
612        // Apply modifier functions to existing container contents
613    }
614
615    /// Sets the container's loot table reference.
616    pub fn set_loot_table(&mut self, _loot_table: &Identifier, _seed: Option<i64>) {
617        // TODO: Implement loot table reference setting
618        // Set CONTAINER_LOOT component with table reference and seed
619    }
620
621    /// Sets attribute modifiers on this item.
622    pub fn set_attributes<R: rand::Rng>(
623        &mut self,
624        _modifiers: &[crate::loot_table::AttributeModifier],
625        _replace: bool,
626        _rng: &mut R,
627    ) {
628        // TODO: Implement attribute modifier setting
629        // Set ATTRIBUTE_MODIFIERS component
630    }
631
632    /// Fills a player head with texture from an entity.
633    pub fn fill_player_head<R: rand::Rng>(
634        &mut self,
635        _entity: crate::loot_table::LootContextEntity,
636        _ctx: &crate::loot_table::LootContext<'_, R>,
637    ) {
638        // TODO: Implement player head texture filling
639        // Get player profile from entity and set PROFILE component
640    }
641
642    /// Copies custom NBT data from a source.
643    pub fn copy_custom_data<R: rand::Rng>(
644        &mut self,
645        _source: crate::loot_table::CopySource,
646        _operations: &[crate::loot_table::CopyDataOperation],
647        _ctx: &crate::loot_table::LootContext<'_, R>,
648    ) {
649        // TODO: Implement custom data copying
650        // Copy NBT paths from source to item's CUSTOM_DATA component
651    }
652
653    /// Sets banner pattern layers.
654    pub fn set_banner_pattern(
655        &mut self,
656        _patterns: &[crate::loot_table::BannerPattern],
657        _append: bool,
658    ) {
659        // TODO: Implement banner pattern setting
660        // Set BANNER_PATTERNS component
661    }
662
663    /// Sets firework rocket properties.
664    pub fn set_fireworks(
665        &mut self,
666        _explosions: Option<&[crate::loot_table::FireworkExplosion]>,
667        _flight_duration: Option<i32>,
668    ) {
669        // TODO: Implement firework setting
670        // Set FIREWORKS component
671    }
672
673    /// Sets firework star explosion properties.
674    pub fn set_firework_explosion(&mut self, _explosion: &crate::loot_table::FireworkExplosion) {
675        // TODO: Implement firework explosion setting
676        // Set FIREWORK_EXPLOSION component
677    }
678
679    /// Sets book cover (title/author for written books).
680    pub fn set_book_cover(
681        &mut self,
682        _title: Option<&str>,
683        _author: Option<&str>,
684        _generation: Option<i32>,
685    ) {
686        // TODO: Implement book cover setting
687        // Set WRITTEN_BOOK_CONTENT component fields
688    }
689
690    /// Sets written book page contents.
691    pub fn set_written_book_pages(
692        &mut self,
693        _pages: &[&str],
694        _mode: crate::loot_table::ListOperation,
695    ) {
696        // TODO: Implement written book pages setting
697        // Set WRITTEN_BOOK_CONTENT pages
698    }
699
700    /// Sets writable book page contents.
701    pub fn set_writable_book_pages(
702        &mut self,
703        _pages: &[&str],
704        _mode: crate::loot_table::ListOperation,
705    ) {
706        // TODO: Implement writable book pages setting
707        // Set WRITABLE_BOOK_CONTENT pages
708    }
709
710    /// Toggles tooltip visibility for components.
711    pub fn toggle_tooltips(&mut self, _toggles: &[(Identifier, bool)]) {
712        // TODO: Implement tooltip toggling
713        // For each component, set its show_in_tooltip flag
714    }
715
716    /// Sets custom model data.
717    pub fn set_custom_model_data(&mut self, _value: i32) {
718        // TODO: Implement custom model data setting
719        // Set CUSTOM_MODEL_DATA component
720    }
721
722    pub fn components_equal(&self, other: &Self) -> bool {
723        let mut all_keys = rustc_hash::FxHashSet::default();
724
725        for key in self.prototype().keys() {
726            if !self.patch.is_removed(key) {
727                all_keys.insert(key);
728            }
729        }
730        for (key, entry) in self.patch.iter() {
731            if matches!(entry, ComponentPatchEntry::Set(_)) {
732                all_keys.insert(key);
733            }
734        }
735        for key in other.prototype().keys() {
736            if !other.patch.is_removed(key) {
737                all_keys.insert(key);
738            }
739        }
740        for (key, entry) in other.patch.iter() {
741            if matches!(entry, ComponentPatchEntry::Set(_)) {
742                all_keys.insert(key);
743            }
744        }
745        for key in all_keys {
746            let val_a = self.get_effective_value_raw(key);
747            let val_b = other.get_effective_value_raw(key);
748
749            match (val_a, val_b) {
750                (Some(a), Some(b)) => {
751                    if a != b {
752                        return false;
753                    }
754                }
755                (None, None) => {}
756                _ => return false,
757            }
758        }
759
760        true
761    }
762}
763
764/// Vanilla unbreaking formula: `1 / (unbreaking_level + 1)` chance to consume durability.
765fn should_consume_durability(unbreaking_level: i32) -> bool {
766    if unbreaking_level <= 0 {
767        return true;
768    }
769    // TODO: Armor uses a different formula: `3 / (unbreaking_level + 3)`
770    rand::rng().random_range(0..unbreaking_level + 1) == 0
771}
772
773impl std::fmt::Display for ItemStack {
774    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
775        if self.is_empty() {
776            write!(f, "Empty")
777        } else {
778            write!(f, "{} {}", self.count, self.item.key)
779        }
780    }
781}
782
783impl WriteTo for ItemStack {
784    fn write(&self, writer: &mut impl Write) -> Result<()> {
785        if self.is_empty() {
786            VarInt(0).write(writer)?;
787        } else {
788            VarInt(self.count).write(writer)?;
789            // Write item ID as VarInt
790            VarInt(self.item.id() as i32).write(writer)?;
791            // Write DataComponentPatch
792            self.patch.write(writer)?;
793        }
794        Ok(())
795    }
796}
797
798impl ReadFrom for ItemStack {
799    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
800        let count = VarInt::read(data)?.0;
801        if count <= 0 {
802            return Ok(Self::empty());
803        }
804
805        let item_id = VarInt::read(data)?.0 as usize;
806        let item = REGISTRY.items.by_id(item_id).unwrap_or(&ITEMS.air);
807
808        // Read DataComponentPatch
809        let patch = DataComponentPatch::read(data)?;
810
811        Ok(Self { item, count, patch })
812    }
813}
814
815impl ItemStack {
816    /// Reads an item stack using the delimited (untrusted) component format.
817    ///
818    /// Vanilla uses this for serverbound packets where component data is
819    /// length-prefixed (e.g., `ServerboundSetCreativeModeSlotPacket`).
820    pub fn read_untrusted(data: &mut Cursor<&[u8]>) -> Result<Self> {
821        let count = VarInt::read(data)?.0;
822        if count <= 0 {
823            return Ok(Self::empty());
824        }
825
826        let item_id = VarInt::read(data)?.0 as usize;
827        let item = REGISTRY.items.by_id(item_id).unwrap_or(&ITEMS.air);
828        let patch = DataComponentPatch::read_delimited(data)?;
829
830        Ok(Self { item, count, patch })
831    }
832}
833
834use simdnbt::{
835    FromNbtTag, ToNbtTag,
836    borrow::{NbtCompound as NbtCompoundView, NbtTag as BorrowedNbtTag},
837    owned::NbtCompound,
838};
839
840impl ToNbtTag for ItemStack {
841    /// Converts this item stack to an NBT tag for persistent storage.
842    ///
843    /// Format (matching vanilla Minecraft):
844    /// ```text
845    /// {
846    ///     id: "minecraft:stone",
847    ///     count: 64,
848    ///     components: { ... }  // Only present if patch is non-empty
849    /// }
850    /// ```
851    fn to_nbt_tag(self) -> simdnbt::owned::NbtTag {
852        self.to_nbt_tag_ref()
853    }
854}
855
856impl ItemStack {
857    /// Converts this item stack to an NBT tag for persistent storage without consuming it.
858    #[must_use]
859    pub fn to_nbt_tag_ref(&self) -> simdnbt::owned::NbtTag {
860        if self.is_empty() {
861            // Empty stacks are represented as an empty compound
862            return simdnbt::owned::NbtTag::Compound(NbtCompound::new());
863        }
864
865        let mut compound = NbtCompound::new();
866
867        // id: The item identifier
868        compound.insert("id", self.item.key.to_string());
869
870        // count: The stack count (vanilla uses Int for NBT storage)
871        compound.insert("count", self.count);
872
873        // components: The component patch (only if non-empty)
874        if !self.patch.is_empty() {
875            compound.insert("components", self.patch.to_nbt_tag_ref());
876        }
877
878        simdnbt::owned::NbtTag::Compound(compound)
879    }
880}
881
882impl FromNbtTag for ItemStack {
883    /// Parses an item stack from an NBT tag.
884    ///
885    /// Accepts the vanilla format:
886    /// ```text
887    /// {
888    ///     id: "minecraft:stone",
889    ///     count: 64,
890    ///     components: { ... }
891    /// }
892    /// ```
893    fn from_nbt_tag(tag: BorrowedNbtTag) -> Option<Self> {
894        let compound = tag.compound()?;
895
896        // Get the item ID
897        let id_str = compound.get("id")?.string()?.to_str();
898        let id = id_str.parse::<Identifier>().ok()?;
899
900        // Look up the item in the registry
901        let item = REGISTRY.items.by_key(&id)?;
902
903        // Get the count (default to 1 if not present)
904        let count = compound.get("count").and_then(|t| t.int()).unwrap_or(1);
905
906        // Parse components if present
907        let patch = compound
908            .get("components")
909            .and_then(DataComponentPatch::from_nbt_tag)
910            .unwrap_or_default();
911
912        Some(Self { item, count, patch })
913    }
914}
915
916impl ItemStack {
917    /// Parses an `ItemStack` from a borrowed `NbtCompoundView`.
918    ///
919    /// This is useful for loading items from disk where we have borrowed NBT data
920    /// and want to avoid the overhead of converting to an owned tag first.
921    #[must_use]
922    pub fn from_borrowed_compound(compound: &NbtCompoundView<'_, '_>) -> Option<Self> {
923        // Get the item ID
924        let id_str = compound.string("id")?.to_str();
925        let id = id_str.parse::<Identifier>().ok()?;
926
927        // Look up the item in the registry
928        let item = REGISTRY.items.by_key(&id)?;
929
930        // Get the count (default to 1 if not present)
931        let count = compound.int("count").unwrap_or(1);
932
933        // Parse components if present
934        let patch = compound
935            .get("components")
936            .and_then(DataComponentPatch::from_nbt_tag)
937            .unwrap_or_default();
938
939        Some(Self::with_count_and_patch(item, count, patch))
940    }
941}