steel_core/behavior/blocks/falling/
concrete_powder_block.rs1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::properties::Direction;
5use steel_registry::blocks::{BlockRef, block_state_ext::BlockStateExt as _};
6use steel_registry::fluid::FluidStateExt as _;
7use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
8
9use crate::behavior::{BlockBehavior, BlockPlaceContext, Fallable};
10use crate::entity::entities::FallingBlockEntity;
11use crate::world::{LevelReader, ScheduledTickAccess, World};
12
13use super::FallingBlock;
14
15#[block_behavior]
17pub struct ConcretePowderBlock {
18 falling: FallingBlock,
19 #[json_arg(vanilla_blocks, json = "concrete")]
20 concrete: BlockRef,
21}
22
23impl ConcretePowderBlock {
24 #[must_use]
26 pub const fn new(block: BlockRef, concrete: BlockRef) -> Self {
27 Self {
28 falling: FallingBlock::new(block),
29 concrete,
30 }
31 }
32
33 fn can_solidify(state: BlockStateId) -> bool {
34 state.get_fluid_state().is_water()
35 }
36
37 fn touches_liquid(world: &dyn LevelReader, pos: BlockPos) -> bool {
38 let mut test_pos = pos;
39 for direction in Direction::ALL {
40 let state_at_test_pos = world.get_block_state(test_pos);
41 if direction == Direction::Down && !Self::can_solidify(state_at_test_pos) {
42 continue;
43 }
44
45 test_pos = pos.relative(direction);
46 let neighbor_state = world.get_block_state(test_pos);
47 if Self::can_solidify(neighbor_state)
48 && !world.is_face_sturdy(neighbor_state, pos, direction.opposite())
49 {
50 return true;
51 }
52 }
53 false
54 }
55
56 fn should_solidify(
57 world: &dyn LevelReader,
58 pos: BlockPos,
59 replaced_state: BlockStateId,
60 ) -> bool {
61 Self::can_solidify(replaced_state) || Self::touches_liquid(world, pos)
62 }
63}
64
65impl Fallable for ConcretePowderBlock {
66 fn on_land(
67 &self,
68 world: &Arc<World>,
69 pos: BlockPos,
70 _state: BlockStateId,
71 replaced_state: BlockStateId,
72 _entity: &FallingBlockEntity,
73 ) {
74 if Self::should_solidify(world.as_ref(), pos, replaced_state) {
75 world.set_block(pos, self.concrete.default_state(), UpdateFlags::UPDATE_ALL);
76 }
77 }
78
79 fn is_concrete_powder(&self) -> bool {
80 true
81 }
82}
83
84impl BlockBehavior for ConcretePowderBlock {
85 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
86 let pos = context.place_pos();
87 let replaced_state = context.world.get_block_state(pos);
88 if Self::should_solidify(context.world.as_ref(), pos, replaced_state) {
89 Some(self.concrete.default_state())
90 } else {
91 Some(self.falling.block().default_state())
92 }
93 }
94
95 fn on_place(
96 &self,
97 _state: BlockStateId,
98 world: &Arc<World>,
99 pos: BlockPos,
100 _old_state: BlockStateId,
101 _moved_by_piston: bool,
102 ) {
103 self.falling.on_place(world, pos);
104 }
105
106 fn update_shape(
107 &self,
108 state: BlockStateId,
109 ticks: &dyn ScheduledTickAccess,
110 pos: BlockPos,
111 _direction: Direction,
112 _neighbor_pos: BlockPos,
113 _neighbor_state: BlockStateId,
114 ) -> BlockStateId {
115 if Self::touches_liquid(ticks, pos) {
116 self.concrete.default_state()
117 } else {
118 self.falling.update_shape(state, ticks, pos)
119 }
120 }
121
122 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
123 let _ = FallingBlock::tick(state, world, pos);
124 }
125
126 fn as_fallable(&self) -> Option<&dyn Fallable> {
127 Some(self)
128 }
129}