Skip to main content

steel_core/behavior/items/
potion.rs

1use std::borrow::Cow;
2
3use crate::behavior::{InteractionResult, ItemBehavior, UseOnContext};
4use crate::world::game_event::GameEventContext;
5use glam::DVec3;
6use steel_macros::item_behavior;
7use steel_protocol::packets::game::SoundSource;
8use steel_registry::blocks::block_state_ext::BlockStateExt;
9use steel_registry::data_components::{PotionContents, vanilla_components};
10use steel_registry::item_stack::ItemStack;
11use steel_registry::particle_type::ParticleData;
12use steel_registry::vanilla_block_tags::BlockTag;
13use steel_registry::{
14    sound_events, vanilla_blocks, vanilla_game_events, vanilla_items, vanilla_particle_types,
15    vanilla_potions,
16};
17use steel_utils::Direction;
18use steel_utils::types::UpdateFlags;
19use text_components::TextComponent;
20
21use super::dynamic_name::potion_name;
22
23/// Potion behavior providing Vanilla's potion-content-dependent name.
24// TODO: Add PotionItem's water default instance when Steel has item-specific
25// default-stack factories.
26// TODO: Complete the shared CONSUMABLE use/finish lifecycle so potions can be
27// drunk.
28#[item_behavior]
29pub struct PotionItem;
30
31impl ItemBehavior for PotionItem {
32    fn get_name<'a>(&self, stack: &'a ItemStack) -> Cow<'a, TextComponent> {
33        potion_name(stack)
34    }
35
36    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
37        if context.hit_result.direction == Direction::Down {
38            return InteractionResult::Pass;
39        }
40
41        let pos = context.hit_result.block_pos;
42        let block_state = context.world.get_block_state(pos);
43        if !block_state
44            .get_block()
45            .has_tag(&BlockTag::CONVERTABLE_TO_MUD)
46        {
47            return InteractionResult::Pass;
48        }
49
50        let was_water = context.inv.with_inventory(|inv| {
51            let potion_contents = inv
52                .get_item_in_hand(context.hand)
53                .get_or_default(vanilla_components::POTION_CONTENTS, PotionContents::empty());
54            if !potion_contents.is(&vanilla_potions::WATER) {
55                return false;
56            }
57            inv.apply_filled_result(
58                context.hand,
59                ItemStack::new(&vanilla_items::GLASS_BOTTLE),
60                context.player.has_infinite_materials(),
61                true,
62            );
63            true
64        });
65
66        if was_water {
67            context.world.play_sound(
68                &sound_events::ENTITY_GENERIC_SPLASH,
69                SoundSource::Blocks,
70                pos,
71                1.0,
72                1.0,
73                None,
74            );
75            for _ in 0..5 {
76                context.world.send_particles(
77                    ParticleData::simple(&vanilla_particle_types::SPLASH),
78                    DVec3::new(
79                        f64::from(pos.x()) + rand::random::<f64>(),
80                        f64::from(pos.y()) + 1.0,
81                        f64::from(pos.z()) + rand::random::<f64>(),
82                    ),
83                    1,
84                    DVec3::ZERO,
85                    1.0,
86                );
87            }
88            context.world.play_sound(
89                &sound_events::ITEM_BOTTLE_EMPTY,
90                SoundSource::Blocks,
91                pos,
92                1.0,
93                1.0,
94                None,
95            );
96            context.world.game_event(
97                &vanilla_game_events::FLUID_PLACE,
98                pos,
99                &GameEventContext::new(None, None),
100            );
101            context.world.set_block(
102                pos,
103                vanilla_blocks::MUD.default_state(),
104                UpdateFlags::UPDATE_ALL,
105            );
106
107            InteractionResult::Success
108        } else {
109            InteractionResult::Pass
110        }
111    }
112}