Skip to main content

steel_core/behavior/items/
lead.rs

1use crate::behavior::{InteractionResult, ItemBehavior, UseOnContext};
2use crate::entity::entities::LeashFenceKnotEntity;
3use crate::entity::{RemovalReason, leashables_leashed_to_holder_in_area_near_position};
4use crate::player::Player;
5use crate::world::World;
6use crate::world::game_event::GameEventContext;
7use std::sync::Arc;
8use steel_macros::item_behavior;
9use steel_registry::blocks::block_state_ext::BlockStateExt;
10use steel_registry::vanilla_block_tags::BlockTag;
11use steel_registry::{sound_events, vanilla_game_events};
12use steel_utils::BlockPos;
13
14/// Vanilla lead item behavior.
15///
16/// The behavior does not handle the main functionality of a lead, but rather its block use functionality:
17/// right-clicking a fence removes the knot from the user and places a knot at the right-clicked fence for
18/// all entities (`Leashable`s) leashed by the user.
19#[item_behavior]
20pub struct LeadItem;
21
22impl LeadItem {
23    /// Binds all leashable mobs attached to a player to the block with the provided position.
24    pub fn bind_player_mobs(
25        player: &Player,
26        world: &Arc<World>,
27        pos: BlockPos,
28    ) -> InteractionResult {
29        let entities_to_leash = leashables_leashed_to_holder_in_area_near_position(
30            world,
31            pos.get_center().into(),
32            player,
33        );
34        if entities_to_leash.is_empty() {
35            return InteractionResult::Pass;
36        }
37
38        let existing_knot_exists = LeashFenceKnotEntity::get_knot(world, pos).is_some();
39        let Some(knot) = LeashFenceKnotEntity::get_or_create_knot(world, pos) else {
40            return InteractionResult::Pass;
41        };
42        let mut any_leashed = false;
43
44        for entity in entities_to_leash {
45            if let Some(leashable) = entity.as_leashable()
46                && leashable.can_have_a_leash_attached_to(knot.as_ref())
47            {
48                leashable.set_leashed_to(&knot);
49                any_leashed = true;
50            }
51        }
52
53        if any_leashed {
54            knot.play_sound(&sound_events::ITEM_LEAD_TIED, 1.0, 1.0);
55            world.game_event(
56                &vanilla_game_events::BLOCK_ATTACH,
57                pos,
58                &GameEventContext::new(Some(player), None),
59            );
60            InteractionResult::SuccessServer
61        } else {
62            if !existing_knot_exists {
63                knot.set_removed(RemovalReason::Discarded);
64            }
65            InteractionResult::Pass
66        }
67    }
68}
69
70impl ItemBehavior for LeadItem {
71    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
72        let pos = context.hit_result.block_pos;
73        let state = context.world.get_block_state(pos);
74        if state.get_block().has_tag(&BlockTag::FENCES) {
75            Self::bind_player_mobs(context.player, context.world, pos)
76        } else {
77            InteractionResult::Pass
78        }
79    }
80}