steel_core/behavior/
item.rs1use 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
18pub trait ItemBehavior: Send + Sync {
25 #[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 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 fn use_on(&self, _context: &mut UseOnContext) -> InteractionResult {
42 InteractionResult::Pass
43 }
44
45 fn use_item(&self, context: &mut UseItemContext) -> InteractionResult {
47 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 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 fn get_item_damage_source(&self, _attacker: &dyn LivingEntity) -> Option<DamageSource> {
90 None
91 }
92
93 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 fn hurt_enemy(
106 &self,
107 _stack: &mut ItemStack,
108 _target: &dyn LivingEntity,
109 _attacker: &dyn LivingEntity,
110 ) {
111 }
112
113 fn post_hurt_enemy(
115 &self,
116 _stack: &mut ItemStack,
117 _target: &dyn LivingEntity,
118 _attacker: &dyn LivingEntity,
119 ) {
120 }
121
122 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
130pub struct ItemBehaviorRegistry {
135 behaviors: Vec<Box<dyn ItemBehavior>>,
136}
137
138impl ItemBehaviorRegistry {
139 #[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 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 #[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 #[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 #[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}