steel_core/behavior/blocks/building/
snow_layer_block.rs1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::block_state_ext::BlockStateExt;
5use steel_registry::blocks::properties::{BlockStateProperties, IntProperty};
6use steel_registry::blocks::{BlockRef, shapes};
7use steel_registry::vanilla_block_tags::BlockTag;
8use steel_registry::{REGISTRY, vanilla_blocks};
9use steel_utils::types::UpdateFlags;
10use steel_utils::{BlockPos, BlockStateId, Direction};
11
12use crate::behavior::block::BlockBehavior;
13use crate::behavior::blocks::building::ice_block::BASE_MELT_LIGHT_LEVEL;
14use crate::behavior::context::BlockPlaceContext;
15use crate::chunk::light::LightLayer;
16use crate::entity::ai::path::PathComputationType;
17use crate::fluid::fluid_state_to_block;
18use crate::world::{LevelReader, ScheduledTickAccess, World};
19
20#[block_behavior]
27pub struct SnowLayerBlock {
28 block: BlockRef,
29}
30
31const LAYERS: IntProperty = BlockStateProperties::LAYERS;
32const MAX_SNOW_LAYERS: u8 = 8;
33
34impl SnowLayerBlock {
35 #[must_use]
37 pub const fn new(block: BlockRef) -> Self {
38 Self { block }
39 }
40}
41
42impl BlockBehavior for SnowLayerBlock {
43 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
44 let below = world.get_block_state(pos.below());
45 let below_block = below.get_block();
46
47 if below_block.has_tag(&BlockTag::CANNOT_SUPPORT_SNOW_LAYER) {
48 return false;
49 }
50
51 if below_block.has_tag(&BlockTag::SUPPORT_OVERRIDE_SNOW_LAYER) {
52 return true;
53 }
54
55 if shapes::is_offset_face_full(below.get_collision_shape_at(pos.below()), Direction::Up) {
56 return true;
57 }
58
59 below_block == self.block && below.get_value(&LAYERS) == MAX_SNOW_LAYERS
61 }
62
63 fn update_shape(
64 &self,
65 state: BlockStateId,
66 world: &dyn ScheduledTickAccess,
67 pos: BlockPos,
68 _direction: Direction,
69 _neighbor_pos: BlockPos,
70 _neighbor_state: BlockStateId,
71 ) -> BlockStateId {
72 if self.can_survive(state, world, pos) {
73 state
74 } else {
75 vanilla_blocks::AIR.default_state()
76 }
77 }
78
79 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
80 if world.light_value_at(LightLayer::Block, pos) > BASE_MELT_LIGHT_LEVEL {
81 world.drop_resources(state, pos);
82 world.set_block(
83 pos,
84 fluid_state_to_block(state.get_fluid_state()),
85 UpdateFlags::UPDATE_ALL,
86 );
87 }
88 }
89
90 fn can_be_replaced(&self, state: BlockStateId, context: &BlockPlaceContext<'_>) -> bool {
91 let layers = state.get_value(&LAYERS);
92 if !context.with_item(|item| item.item() == REGISTRY.items.by_block(state.get_block()))
93 || layers >= MAX_SNOW_LAYERS
94 {
95 return layers == 1;
96 }
97 if context.replaces_clicked_block() {
98 return context.clicked_face() == Direction::Up;
99 }
100 true
101 }
102
103 fn is_pathfindable(&self, state: BlockStateId, computation_type: PathComputationType) -> bool {
104 computation_type == PathComputationType::Land && state.get_value(&LAYERS) < 5
105 }
106
107 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
108 let pos = if context.replaces_clicked_block() {
109 context.hit_pos()
110 } else {
111 context.place_pos()
112 };
113 let state = context.world.get_block_state(pos);
114 if state.get_block() == self.block {
115 let layers = state.get_value(&LAYERS);
116 return Some(state.set_value(&LAYERS, MAX_SNOW_LAYERS.min(layers + 1)));
117 }
118 Some(self.block.default_state())
119 }
120}