Skip to main content

steel_core/behavior/blocks/building/
snow_layer_block.rs

1use 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/// Vanilla `SnowLayerBlock` survival.
21///
22/// 1. If below is in `cannot_support_snow_layer`, false.
23/// 2. If below is in `support_override_snow_layer`, true.
24/// 3. Otherwise: below's collision shape has a full UP face, or below is snow
25///    with `LAYERS = 8`.
26#[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    /// Creates a new snow layer block behavior.
36    #[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 is another snow layer fully filled (LAYERS == 8).
60        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}