steel_core/behavior/blocks/container/
furnace_block.rs1use 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]
28pub struct FurnaceBlock {
30 block: BlockRef,
31}
32
33#[block_behavior]
34pub struct BlastFurnaceBlock {
36 block: BlockRef,
37}
38
39#[block_behavior]
40pub 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 #[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);