Skip to main content

steel_core/behavior/items/
brush.rs

1//! Brush item behavior for continuous archaeology brushing.
2
3use std::sync::Arc;
4
5use steel_macros::item_behavior;
6use steel_registry::item_stack::ItemStack;
7use steel_registry::sound_events;
8use steel_utils::Downcast as _;
9use steel_utils::types::InteractionHand;
10use steel_utils::{BlockPos, Direction};
11
12use crate::behavior::context::{InteractionResult, UseOnContext};
13use crate::behavior::{BLOCK_BEHAVIORS, ItemBehavior, ItemUseAnimation};
14use crate::block_entity::entities::BrushableBlockEntity;
15use crate::entity::projectile::{ViewVectorHitResult, get_hit_result_on_view_vector};
16use crate::entity::{Entity, LivingEntity};
17use crate::inventory::equipment::EquipmentSlot;
18use crate::player::Player;
19use crate::world::World;
20
21const USE_DURATION: i32 = 200;
22const ANIMATION_DURATION: i32 = 10;
23const SOUND_TICK_OFFSET: i32 = 5;
24
25/// Vanilla brush item behavior for continuous archaeology brushing.
26#[item_behavior]
27pub struct BrushItem;
28
29impl ItemBehavior for BrushItem {
30    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
31        if calculate_block_hit(context.world, context.player).is_some() {
32            context.player.start_using_item(context.hand);
33        }
34        InteractionResult::Consume
35    }
36
37    fn get_use_duration(&self, _stack: &ItemStack, _user: &dyn LivingEntity) -> i32 {
38        USE_DURATION
39    }
40
41    fn get_use_animation(&self, _stack: &ItemStack) -> ItemUseAnimation {
42        ItemUseAnimation::Brush
43    }
44
45    fn on_use_tick(
46        &self,
47        world: &Arc<World>,
48        user: &dyn LivingEntity,
49        stack: &mut ItemStack,
50        ticks_remaining: i32,
51    ) {
52        if ticks_remaining < 0 {
53            release_player_use(user);
54            return;
55        }
56
57        let Some(player) = user.as_player() else {
58            return;
59        };
60        let Some((pos, direction)) = calculate_block_hit(world, player) else {
61            player.release_using_item();
62            return;
63        };
64
65        let elapsed = USE_DURATION - ticks_remaining + 1;
66        if elapsed % ANIMATION_DURATION != SOUND_TICK_OFFSET {
67            return;
68        }
69
70        let state = world.get_block_state(pos);
71        let sound = BLOCK_BEHAVIORS
72            .get_behavior_for_state(state)
73            .and_then(|behavior| behavior.brushable_data(state))
74            .map_or(&sound_events::ITEM_BRUSH_BRUSHING_GENERIC, |data| {
75                data.brush_sound
76            });
77        world.play_block_sound(sound, pos, 1.0, 1.0, Some(player.id()));
78
79        let Some(block_entity) = world.get_block_entity(pos) else {
80            return;
81        };
82        let Some(brushable) = block_entity.downcast_ref::<BrushableBlockEntity>() else {
83            return;
84        };
85        let outcome = brushable.brush(world.game_time(), world, player, direction, stack);
86        outcome.mutation.apply(world, pos);
87
88        if outcome.durability_damage {
89            let slot = equipped_brush_slot(player, stack);
90            let item_ref = stack.item;
91            if stack.hurt_and_break(1, player.has_infinite_materials()) {
92                player.on_equipped_item_broken(item_ref, slot);
93            }
94        }
95    }
96}
97
98fn calculate_block_hit(world: &World, player: &Player) -> Option<(BlockPos, Direction)> {
99    let distance = player.block_interaction_range();
100    match get_hit_result_on_view_vector(world, player, distance, Entity::is_pickable) {
101        ViewVectorHitResult::Block(hit) => Some((hit.block_pos, hit.direction)),
102        ViewVectorHitResult::Miss | ViewVectorHitResult::Entity(_) => None,
103    }
104}
105
106fn equipped_brush_slot(player: &Player, _stack: &ItemStack) -> EquipmentSlot {
107    match player.active_item_use_hand() {
108        Some(InteractionHand::OffHand) => EquipmentSlot::OffHand,
109        Some(InteractionHand::MainHand) | None => EquipmentSlot::MainHand,
110    }
111}
112
113fn release_player_use(user: &dyn LivingEntity) {
114    if let Some(player) = user.as_player() {
115        player.release_using_item();
116    }
117}