Skip to main content

steel_core/behavior/items/
potion.rs

1use std::borrow::Cow;
2use std::sync::Arc;
3
4use crate::behavior::item::finish_consuming_stack;
5use crate::behavior::{InteractionResult, ItemBehavior, UseOnContext};
6use crate::entity::{LivingEntityRef, apply_potion_contents};
7use crate::world::World;
8use crate::world::game_event::GameEventContext;
9use glam::DVec3;
10use steel_macros::item_behavior;
11use steel_protocol::packets::game::SoundSource;
12use steel_registry::blocks::block_state_ext::BlockStateExt;
13use steel_registry::data_components::{PotionContents, vanilla_components};
14use steel_registry::item_stack::ItemStack;
15use steel_registry::items::ItemRef;
16use steel_registry::particle_type::ParticleData;
17use steel_registry::vanilla_block_tags::BlockTag;
18use steel_registry::{
19    sound_events, vanilla_blocks, vanilla_game_events, vanilla_items, vanilla_particle_types,
20    vanilla_potions,
21};
22use steel_utils::Direction;
23use steel_utils::types::UpdateFlags;
24use text_components::TextComponent;
25
26use super::dynamic_name::potion_name;
27
28/// Splash particles spawned when a water bottle turns a block into mud.
29const MUD_SPLASH_PARTICLE_COUNT: usize = 5;
30
31/// Potion behavior providing Vanilla's potion-content-dependent name and
32/// water default instance.
33#[item_behavior]
34pub struct PotionItem;
35
36impl ItemBehavior for PotionItem {
37    fn get_name<'a>(&self, stack: &'a ItemStack) -> Cow<'a, TextComponent> {
38        potion_name(stack)
39    }
40
41    fn finish_using(
42        &self,
43        stack: &mut ItemStack,
44        world: &Arc<World>,
45        user: LivingEntityRef<'_>,
46    ) -> ItemStack {
47        let contents =
48            stack.get_or_default(vanilla_components::POTION_CONTENTS, PotionContents::empty());
49        let duration_scale = stack.get_or_default(vanilla_components::POTION_DURATION_SCALE, 1.0);
50        apply_potion_contents(&contents, world, user, duration_scale);
51        finish_consuming_stack(stack, world, user.living())
52    }
53
54    fn get_default_instance(&self, item: ItemRef) -> ItemStack {
55        potion_default_instance(item)
56    }
57
58    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
59        potion_use_on(context)
60    }
61}
62
63pub(super) fn potion_default_instance(item: ItemRef) -> ItemStack {
64    PotionContents::create_item_stack(item, &vanilla_potions::WATER)
65}
66
67/// Converts the block hit into mud when the held stack is a water bottle
68/// over a `CONVERTIBLE_TO_MUD` block.
69pub(super) fn potion_use_on(context: &mut UseOnContext) -> InteractionResult {
70    if context.hit_result.direction == Direction::Down {
71        return InteractionResult::Pass;
72    }
73
74    let pos = context.hit_result.block_pos;
75    let block_state = context.world.get_block_state(pos);
76    if !block_state
77        .get_block()
78        .has_tag(&BlockTag::CONVERTIBLE_TO_MUD)
79    {
80        return InteractionResult::Pass;
81    }
82
83    let is_water = context.inv.with_inventory(|inv| {
84        inv.get_item_in_hand(context.hand)
85            .get_or_default(vanilla_components::POTION_CONTENTS, PotionContents::empty())
86            .is(&vanilla_potions::WATER)
87    });
88    if !is_water {
89        return InteractionResult::Pass;
90    }
91
92    context.world.play_sound(
93        &sound_events::ENTITY_GENERIC_SPLASH,
94        SoundSource::Blocks,
95        pos,
96        1.0,
97        1.0,
98        None,
99    );
100    context.inv.with_inventory(|inv| {
101        inv.apply_filled_result(
102            context.hand,
103            ItemStack::new(&vanilla_items::GLASS_BOTTLE),
104            context.player.has_infinite_materials(),
105            true,
106        );
107    });
108    for _ in 0..MUD_SPLASH_PARTICLE_COUNT {
109        context.world.send_particles(
110            ParticleData::simple(&vanilla_particle_types::SPLASH),
111            DVec3::new(
112                f64::from(pos.x()) + rand::random::<f64>(),
113                f64::from(pos.y()) + 1.0,
114                f64::from(pos.z()) + rand::random::<f64>(),
115            ),
116            1,
117            DVec3::ZERO,
118            1.0,
119        );
120    }
121    context.world.play_sound(
122        &sound_events::ITEM_BOTTLE_EMPTY,
123        SoundSource::Blocks,
124        pos,
125        1.0,
126        1.0,
127        None,
128    );
129    context.world.game_event(
130        &vanilla_game_events::FLUID_PLACE,
131        pos,
132        &GameEventContext::new(None, None),
133    );
134    context.world.set_block(
135        pos,
136        vanilla_blocks::MUD.default_state(),
137        UpdateFlags::UPDATE_ALL,
138    );
139
140    InteractionResult::Success
141}