Skip to main content

steel_core/behavior/blocks/container/
furnace_block.rs

1//! Furnace, blast-furnace, and smoker block behavior.
2
3use std::sync::{Arc, Weak};
4
5use steel_macros::block_behavior;
6use steel_registry::block_entity_type::BlockEntityTypeRef;
7use steel_registry::blocks::BlockRef;
8use steel_registry::blocks::block_state_ext::BlockStateExt as _;
9use steel_registry::blocks::properties::{BlockStateProperties, Direction, EnumProperty};
10use steel_registry::stat::custom::CustomStatRef;
11use steel_registry::{vanilla_block_entity_types, vanilla_custom_stats};
12use steel_utils::{BlockPos, BlockStateId, translations};
13use text_components::TextComponent;
14
15use crate::behavior::block::{BlockBehavior, BlockEntityCreation};
16use crate::behavior::{BlockHitResult, BlockPlaceContext, InteractionResult, InventoryAccess};
17use crate::block_entity::entities::FurnaceKind;
18use crate::block_entity::{BLOCK_ENTITIES, BlockEntityTicker};
19use crate::inventory::container::calculate_redstone_signal_from_container;
20use crate::inventory::lock::{ContainerLockGuard, ContainerRef};
21use crate::inventory::menu::kinds::furnace_menu;
22use crate::player::Player;
23use crate::world::{LevelReader, World};
24
25const FACING: &EnumProperty<Direction> = &BlockStateProperties::HORIZONTAL_FACING;
26
27#[block_behavior]
28/// Vanilla furnace block behavior.
29pub struct FurnaceBlock {
30    block: BlockRef,
31}
32
33#[block_behavior]
34/// Vanilla blast-furnace block behavior.
35pub struct BlastFurnaceBlock {
36    block: BlockRef,
37}
38
39#[block_behavior]
40/// Vanilla smoker block behavior.
41pub struct SmokerBlock {
42    block: BlockRef,
43}
44
45fn placement_state(block: BlockRef, context: &BlockPlaceContext<'_>) -> BlockStateId {
46    block
47        .default_state()
48        .set_value(FACING, context.horizontal_direction().opposite())
49}
50
51fn open_furnace(
52    world: &Arc<World>,
53    pos: BlockPos,
54    player: &Player,
55    expected_type: BlockEntityTypeRef,
56    kind: FurnaceKind,
57    title: TextComponent,
58    stat: CustomStatRef,
59) -> InteractionResult {
60    let Some(block_entity) = world.get_block_entity(pos) else {
61        return InteractionResult::Success;
62    };
63    if block_entity.get_type() != expected_type {
64        return InteractionResult::Success;
65    }
66    let Some(container) = ContainerRef::from_block_entity(block_entity) else {
67        return InteractionResult::Success;
68    };
69    let inventory = player.inventory.clone();
70    player.open_menu(title, move |context| {
71        furnace_menu(inventory, context.container_id, container, kind)
72    });
73    player.award_custom_stat(stat);
74    InteractionResult::Success
75}
76
77fn analog_output(world: &dyn LevelReader, pos: BlockPos) -> i32 {
78    let Some(container) = world
79        .get_block_entity(pos)
80        .and_then(ContainerRef::from_block_entity)
81    else {
82        return 0;
83    };
84    let guard = ContainerLockGuard::lock_all(&[&container]);
85    guard
86        .get(container.container_id())
87        .map_or(0, calculate_redstone_signal_from_container)
88}
89
90macro_rules! impl_furnace_block {
91    ($name:ident, $entity_type:ident, $kind:ident, $title:ident, $stat:ident) => {
92        impl $name {
93            /// Creates this behavior for its registered block.
94            #[must_use]
95            pub const fn new(block: BlockRef) -> Self {
96                Self { block }
97            }
98        }
99
100        impl BlockBehavior for $name {
101            fn get_state_for_placement(
102                &self,
103                context: &BlockPlaceContext<'_>,
104            ) -> Option<BlockStateId> {
105                Some(placement_state(self.block, context))
106            }
107
108            fn use_without_item(
109                &self,
110                _state: BlockStateId,
111                world: &Arc<World>,
112                pos: BlockPos,
113                player: &Player,
114                _hit_result: &BlockHitResult,
115                _inv: &mut InventoryAccess,
116            ) -> InteractionResult {
117                open_furnace(
118                    world,
119                    pos,
120                    player,
121                    &vanilla_block_entity_types::$entity_type,
122                    FurnaceKind::$kind,
123                    TextComponent::translated(translations::$title.msg()),
124                    &vanilla_custom_stats::$stat,
125                )
126            }
127
128            fn new_block_entity(
129                &self,
130                level: Weak<World>,
131                pos: BlockPos,
132                state: BlockStateId,
133            ) -> BlockEntityCreation {
134                BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
135                    &vanilla_block_entity_types::$entity_type,
136                    level,
137                    pos,
138                    state,
139                ))
140            }
141
142            fn get_block_entity_ticker(
143                &self,
144                _world: &Arc<World>,
145                _state: BlockStateId,
146                block_entity_type: BlockEntityTypeRef,
147            ) -> Option<BlockEntityTicker> {
148                BlockEntityTicker::for_matching_entity_tick(
149                    block_entity_type,
150                    &vanilla_block_entity_types::$entity_type,
151                )
152            }
153
154            fn affect_neighbors_after_removal(
155                &self,
156                _state: BlockStateId,
157                world: &Arc<World>,
158                pos: BlockPos,
159                _moved_by_piston: bool,
160            ) {
161                world.update_neighbor_for_output_signal(pos, self.block);
162            }
163
164            fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
165                true
166            }
167
168            fn get_analog_output_signal(
169                &self,
170                _state: BlockStateId,
171                world: &dyn LevelReader,
172                pos: BlockPos,
173                _direction: Direction,
174            ) -> i32 {
175                analog_output(world, pos)
176            }
177        }
178    };
179}
180
181impl_furnace_block!(
182    FurnaceBlock,
183    FURNACE,
184    Furnace,
185    CONTAINER_FURNACE,
186    INTERACT_WITH_FURNACE
187);
188impl_furnace_block!(
189    BlastFurnaceBlock,
190    BLAST_FURNACE,
191    BlastFurnace,
192    CONTAINER_BLAST_FURNACE,
193    INTERACT_WITH_BLAST_FURNACE
194);
195impl_furnace_block!(
196    SmokerBlock,
197    SMOKER,
198    Smoker,
199    CONTAINER_SMOKER,
200    INTERACT_WITH_SMOKER
201);