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::LivingEntity;
7use crate::entity::apply_potion_contents;
8use crate::world::World;
9use crate::world::game_event::GameEventContext;
10use glam::DVec3;
11use steel_macros::item_behavior;
12use steel_protocol::packets::game::SoundSource;
13use steel_registry::blocks::block_state_ext::BlockStateExt;
14use steel_registry::data_components::{PotionContents, vanilla_components};
15use steel_registry::item_stack::ItemStack;
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/// Potion behavior providing Vanilla's potion-content-dependent name.
29// TODO: Add PotionItem's water default instance when Steel has item-specific
30// default-stack factories.
31#[item_behavior]
32pub struct PotionItem;
33
34impl ItemBehavior for PotionItem {
35    fn get_name<'a>(&self, stack: &'a ItemStack) -> Cow<'a, TextComponent> {
36        potion_name(stack)
37    }
38
39    fn finish_using(
40        &self,
41        stack: &mut ItemStack,
42        world: &Arc<World>,
43        user: &dyn LivingEntity,
44    ) -> ItemStack {
45        let contents =
46            stack.get_or_default(vanilla_components::POTION_CONTENTS, PotionContents::empty());
47        let duration_scale = stack.get_or_default(vanilla_components::POTION_DURATION_SCALE, 1.0);
48        apply_potion_contents(&contents, world, user, duration_scale);
49        finish_consuming_stack(stack, world, user)
50    }
51
52    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
53        if context.hit_result.direction == Direction::Down {
54            return InteractionResult::Pass;
55        }
56
57        let pos = context.hit_result.block_pos;
58        let block_state = context.world.get_block_state(pos);
59        if !block_state
60            .get_block()
61            .has_tag(&BlockTag::CONVERTABLE_TO_MUD)
62        {
63            return InteractionResult::Pass;
64        }
65
66        let was_water = context.inv.with_inventory(|inv| {
67            let potion_contents = inv
68                .get_item_in_hand(context.hand)
69                .get_or_default(vanilla_components::POTION_CONTENTS, PotionContents::empty());
70            if !potion_contents.is(&vanilla_potions::WATER) {
71                return false;
72            }
73            inv.apply_filled_result(
74                context.hand,
75                ItemStack::new(&vanilla_items::GLASS_BOTTLE),
76                context.player.has_infinite_materials(),
77                true,
78            );
79            true
80        });
81
82        if was_water {
83            context.world.play_sound(
84                &sound_events::ENTITY_GENERIC_SPLASH,
85                SoundSource::Blocks,
86                pos,
87                1.0,
88                1.0,
89                None,
90            );
91            for _ in 0..5 {
92                context.world.send_particles(
93                    ParticleData::simple(&vanilla_particle_types::SPLASH),
94                    DVec3::new(
95                        f64::from(pos.x()) + rand::random::<f64>(),
96                        f64::from(pos.y()) + 1.0,
97                        f64::from(pos.z()) + rand::random::<f64>(),
98                    ),
99                    1,
100                    DVec3::ZERO,
101                    1.0,
102                );
103            }
104            context.world.play_sound(
105                &sound_events::ITEM_BOTTLE_EMPTY,
106                SoundSource::Blocks,
107                pos,
108                1.0,
109                1.0,
110                None,
111            );
112            context.world.game_event(
113                &vanilla_game_events::FLUID_PLACE,
114                pos,
115                &GameEventContext::new(None, None),
116            );
117            context.world.set_block(
118                pos,
119                vanilla_blocks::MUD.default_state(),
120                UpdateFlags::UPDATE_ALL,
121            );
122
123            InteractionResult::Success
124        } else {
125            InteractionResult::Pass
126        }
127    }
128}