1use 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#[derive(Debug, Clone, PartialEq)]
29pub struct ItemStack {
30 pub item: ItemRef,
32 pub count: i32,
34 patch: DataComponentPatch,
36}
37
38impl Default for ItemStack {
39 fn default() -> Self {
40 Self::empty()
41 }
42}
43
44impl ItemStack {
45 #[must_use]
47 pub fn empty() -> Self {
48 Self {
49 item: &ITEMS.air,
50 count: 0,
51 patch: DataComponentPatch::new(),
52 }
53 }
54
55 #[must_use]
57 pub fn new(item: ItemRef) -> Self {
58 Self::with_count(item, 1)
59 }
60
61 #[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 #[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 pub fn grow(&mut self, amount: i32) {
107 self.count += amount;
108 }
109
110 pub fn shrink(&mut self, amount: i32) {
112 self.count -= amount;
113 }
114
115 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 #[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 #[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 #[must_use]
155 pub fn is_damageable_item(&self) -> bool {
156 self.has(MAX_DAMAGE) && !self.has(UNBREAKABLE) && self.has(DAMAGE)
157 }
158
159 #[must_use]
161 pub fn is_damaged(&self) -> bool {
162 self.is_damageable_item() && self.get_damage_value() > 0
163 }
164
165 #[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 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 #[must_use]
182 pub fn get_max_damage(&self) -> i32 {
183 self.get(MAX_DAMAGE).copied().unwrap_or(0)
184 }
185
186 #[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 #[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 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 self.set_damage_value(new_damage);
228
229 if self.is_broken() {
230 self.shrink(1);
234 return true;
235 }
236
237 false
238 }
239
240 #[must_use]
242 pub fn has<T: 'static>(&self, component: DataComponentType<T>) -> bool {
243 self.has_component(&component.key)
244 }
245
246 #[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 #[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 #[must_use]
289 pub fn get_equippable(&self) -> Option<&Equippable> {
290 self.get(EQUIPPABLE)
291 }
292
293 #[must_use]
295 pub fn get_equippable_slot(&self) -> Option<EquippableSlot> {
296 self.get_equippable().map(|e| e.slot)
297 }
298
299 #[must_use]
301 pub fn is_equippable_in_slot(&self, slot: EquippableSlot) -> bool {
302 self.get_equippable_slot() == Some(slot)
303 }
304
305 #[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 #[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 #[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 pub fn set<T: Component>(&mut self, component: DataComponentType<T>, value: T) {
331 self.patch.set(component, value);
332 }
333
334 pub fn remove<T: 'static>(&mut self, component: DataComponentType<T>) {
337 self.patch.remove(component);
338 }
339
340 pub fn clear<T: 'static>(&mut self, component: DataComponentType<T>) {
343 self.patch.clear(component);
344 }
345
346 #[must_use]
348 pub fn patch(&self) -> &DataComponentPatch {
349 &self.patch
350 }
351
352 #[must_use]
354 pub fn get_tool(&self) -> Option<&Tool> {
355 self.get(TOOL)
356 }
357
358 #[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 #[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 #[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 #[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 pub fn set_damage_fraction(&mut self, _fraction: f32, _add: bool) {
408 }
413
414 pub fn enchant_randomly<R: rand::Rng>(
416 &mut self,
417 _options: &crate::loot_table::EnchantmentOptions,
418 _rng: &mut R,
419 ) {
420 }
427
428 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 }
442
443 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 }
454
455 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 }
466
467 pub fn set_components_from_json(&mut self, _components: &str) {
469 }
472
473 pub fn set_custom_data(&mut self, _tag: &str) {
475 }
480
481 pub fn apply_furnace_smelt(&mut self) {
483 }
488
489 pub fn create_exploration_map(
491 &mut self,
492 _destination: &Identifier,
493 _decoration: &Identifier,
494 _zoom: i32,
495 _skip_existing_chunks: bool,
496 ) {
497 }
503
504 pub fn set_name(&mut self, _name: &str, _target: crate::loot_table::NameTarget) {
506 }
509
510 pub fn set_ominous_bottle_amplifier(&mut self, _amplifier: i32) {
512 }
515
516 pub fn set_potion(&mut self, _id: &Identifier) {
518 }
521
522 pub fn set_stew_effects<R: rand::Rng>(
524 &mut self,
525 _effects: &[crate::loot_table::StewEffect],
526 _rng: &mut R,
527 ) {
528 }
532
533 pub fn set_instrument<R: rand::Rng>(&mut self, _options: &Identifier, _rng: &mut R) {
535 }
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 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 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 }
573 }
574
575 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 }
585
586 pub fn set_lore(&mut self, _lore: &[&str], _mode: crate::loot_table::ListOperation) {
588 }
592
593 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 }
603
604 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 }
614
615 pub fn set_loot_table(&mut self, _loot_table: &Identifier, _seed: Option<i64>) {
617 }
620
621 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 }
631
632 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 }
641
642 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 }
652
653 pub fn set_banner_pattern(
655 &mut self,
656 _patterns: &[crate::loot_table::BannerPattern],
657 _append: bool,
658 ) {
659 }
662
663 pub fn set_fireworks(
665 &mut self,
666 _explosions: Option<&[crate::loot_table::FireworkExplosion]>,
667 _flight_duration: Option<i32>,
668 ) {
669 }
672
673 pub fn set_firework_explosion(&mut self, _explosion: &crate::loot_table::FireworkExplosion) {
675 }
678
679 pub fn set_book_cover(
681 &mut self,
682 _title: Option<&str>,
683 _author: Option<&str>,
684 _generation: Option<i32>,
685 ) {
686 }
689
690 pub fn set_written_book_pages(
692 &mut self,
693 _pages: &[&str],
694 _mode: crate::loot_table::ListOperation,
695 ) {
696 }
699
700 pub fn set_writable_book_pages(
702 &mut self,
703 _pages: &[&str],
704 _mode: crate::loot_table::ListOperation,
705 ) {
706 }
709
710 pub fn toggle_tooltips(&mut self, _toggles: &[(Identifier, bool)]) {
712 }
715
716 pub fn set_custom_model_data(&mut self, _value: i32) {
718 }
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
764fn should_consume_durability(unbreaking_level: i32) -> bool {
766 if unbreaking_level <= 0 {
767 return true;
768 }
769 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 VarInt(self.item.id() as i32).write(writer)?;
791 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 let patch = DataComponentPatch::read(data)?;
810
811 Ok(Self { item, count, patch })
812 }
813}
814
815impl ItemStack {
816 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 fn to_nbt_tag(self) -> simdnbt::owned::NbtTag {
852 self.to_nbt_tag_ref()
853 }
854}
855
856impl ItemStack {
857 #[must_use]
859 pub fn to_nbt_tag_ref(&self) -> simdnbt::owned::NbtTag {
860 if self.is_empty() {
861 return simdnbt::owned::NbtTag::Compound(NbtCompound::new());
863 }
864
865 let mut compound = NbtCompound::new();
866
867 compound.insert("id", self.item.key.to_string());
869
870 compound.insert("count", self.count);
872
873 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 fn from_nbt_tag(tag: BorrowedNbtTag) -> Option<Self> {
894 let compound = tag.compound()?;
895
896 let id_str = compound.get("id")?.string()?.to_str();
898 let id = id_str.parse::<Identifier>().ok()?;
899
900 let item = REGISTRY.items.by_key(&id)?;
902
903 let count = compound.get("count").and_then(|t| t.int()).unwrap_or(1);
905
906 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 #[must_use]
922 pub fn from_borrowed_compound(compound: &NbtCompoundView<'_, '_>) -> Option<Self> {
923 let id_str = compound.string("id")?.to_str();
925 let id = id_str.parse::<Identifier>().ok()?;
926
927 let item = REGISTRY.items.by_key(&id)?;
929
930 let count = compound.int("count").unwrap_or(1);
932
933 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}