Skip to main content

steel_core/behavior/
item.rs

1//! Item behavior trait and registry.
2
3use std::borrow::Cow;
4
5use steel_registry::data_components::vanilla_components::ITEM_NAME;
6use steel_registry::item_stack::ItemStack;
7use steel_registry::items::ItemRef;
8use steel_registry::{REGISTRY, RegistryEntry, RegistryExt};
9use steel_utils::types::InteractionHand;
10use text_components::TextComponent;
11
12use crate::behavior::items::DefaultItemBehavior;
13use crate::behavior::{InteractionResult, UseItemContext, UseOnContext};
14use crate::entity::damage::DamageSource;
15use crate::entity::{Entity, LivingEntity};
16use crate::player::{Player, player_inventory::EquipmentSwapResult};
17
18/// Trait defining the behavior of an item.
19///
20/// This trait handles dynamic/functional aspects of items:
21/// - Use on blocks (placing, interacting)
22/// - Use in air
23/// - etc.
24pub trait ItemBehavior: Send + Sync {
25    /// Returns the Rust type name of the concrete behavior implementation.
26    #[cfg(feature = "flint")]
27    #[must_use]
28    #[expect(clippy::absolute_paths, reason = "easier for features")]
29    fn type_name(&self) -> &'static str {
30        std::any::type_name::<Self>()
31    }
32
33    /// Returns vanilla `Item.getName(stack)`.
34    fn get_name<'a>(&self, stack: &'a ItemStack) -> Cow<'a, TextComponent> {
35        stack
36            .get(ITEM_NAME)
37            .map_or_else(|| Cow::Owned(TextComponent::new()), Cow::Borrowed)
38    }
39
40    /// Called when this item is used on a block.
41    fn use_on(&self, _context: &mut UseOnContext) -> InteractionResult {
42        InteractionResult::Pass
43    }
44
45    /// Called when this item is used (e.g. right click in air).
46    fn use_item(&self, context: &mut UseItemContext) -> InteractionResult {
47        // TODO: Mirror Item.use/finishUsingItem for CONSUMABLE, BLOCKS_ATTACKS, and
48        // KINETIC_WEAPON so specialized behaviors inherit the complete Vanilla base path.
49        let Some(equippable) = context.inv.with_item(|item| item.get_equippable().cloned()) else {
50            return InteractionResult::Pass;
51        };
52
53        if !equippable.swappable || !equippable.can_be_equipped_by(context.player.entity_type()) {
54            return InteractionResult::Pass;
55        }
56
57        let slot = equippable.slot;
58        let result = context.inv.with_inventory(|inventory| {
59            inventory.try_swap_with_equipment_slot(
60                context.hand,
61                slot,
62                context.player.has_infinite_materials(),
63            )
64        });
65
66        match result {
67            EquipmentSwapResult::Success(overflow) => {
68                if !overflow.is_empty() {
69                    let _ = context.player.drop_item(overflow, false, false);
70                }
71                InteractionResult::Success
72            }
73            EquipmentSwapResult::Fail => InteractionResult::Fail,
74        }
75    }
76
77    /// Called by vanilla `ItemStack.interactLivingEntity`.
78    fn interact_living_entity(
79        &self,
80        _stack: &mut ItemStack,
81        _player: &Player,
82        _target: &dyn LivingEntity,
83        _hand: InteractionHand,
84    ) -> InteractionResult {
85        InteractionResult::Pass
86    }
87
88    /// Returns vanilla `Item.getItemDamageSource`.
89    fn get_item_damage_source(&self, _attacker: &dyn LivingEntity) -> Option<DamageSource> {
90        None
91    }
92
93    /// Returns item-specific attack damage added by `Item.getAttackDamageBonus`.
94    fn get_attack_damage_bonus(
95        &self,
96        _attacker: &dyn LivingEntity,
97        _victim: &dyn Entity,
98        _base_damage: f32,
99        _damage_source: &DamageSource,
100    ) -> f32 {
101        0.0
102    }
103
104    /// Called by vanilla `Item.hurtEnemy`.
105    fn hurt_enemy(
106        &self,
107        _stack: &mut ItemStack,
108        _target: &dyn LivingEntity,
109        _attacker: &dyn LivingEntity,
110    ) {
111    }
112
113    /// Called by vanilla `Item.postHurtEnemy`.
114    fn post_hurt_enemy(
115        &self,
116        _stack: &mut ItemStack,
117        _target: &dyn LivingEntity,
118        _attacker: &dyn LivingEntity,
119    ) {
120    }
121
122    /// Returns how much durability this weapon consumes after a successful entity hit.
123    fn item_damage_per_attack(&self, stack: &ItemStack) -> Option<i32> {
124        stack
125            .get_weapon()
126            .map(|weapon| weapon.item_damage_per_attack)
127    }
128}
129
130/// Registry for item behaviors.
131///
132/// Created after the main registry is frozen. Block items get `BlockItemBehavior`,
133/// other items get `DefaultItemBehavior`. Custom behaviors can be registered.
134pub struct ItemBehaviorRegistry {
135    behaviors: Vec<Box<dyn ItemBehavior>>,
136}
137
138impl ItemBehaviorRegistry {
139    /// Creates a new behavior registry with default behaviors for all items.
140    ///
141    /// Call `register_item_behaviors()` after this to set up proper behaviors.
142    #[must_use]
143    pub fn new() -> Self {
144        let item_count = REGISTRY.items.len();
145        let behaviors = (0..item_count)
146            .map(|_| Box::new(DefaultItemBehavior) as Box<dyn ItemBehavior>)
147            .collect();
148
149        Self { behaviors }
150    }
151
152    /// Sets a custom behavior for an item.
153    pub fn set_behavior(&mut self, item: ItemRef, behavior: Box<dyn ItemBehavior>) {
154        let id = item.id();
155        self.behaviors[id] = behavior;
156    }
157
158    /// Gets the behavior for an item.
159    #[must_use]
160    pub fn get_behavior(&self, item: ItemRef) -> &dyn ItemBehavior {
161        let id = item.id();
162        self.behaviors[id].as_ref()
163    }
164
165    /// Returns vanilla `ItemStack.getHoverName`, including item-specific
166    /// `Item.getName(stack)` overrides when no custom name is present.
167    #[must_use]
168    pub fn hover_name<'a>(&self, stack: &'a ItemStack) -> Cow<'a, TextComponent> {
169        stack
170            .custom_name()
171            .unwrap_or_else(|| self.get_behavior(stack.item()).get_name(stack))
172    }
173
174    /// Get all behaviors.
175    #[cfg(feature = "flint")]
176    #[must_use]
177    pub fn get_behaviors(&self) -> &[Box<dyn ItemBehavior>] {
178        &self.behaviors
179    }
180}
181
182impl Default for ItemBehaviorRegistry {
183    fn default() -> Self {
184        Self::new()
185    }
186}