steel_core/behavior/blocks/building/
ice_block.rs1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::BlockRef;
5use steel_registry::blocks::block_state_ext::BlockStateExt;
6use steel_registry::item_stack::ItemStack;
7use steel_registry::vanilla_enchantment_tags::EnchantmentTag;
8use steel_registry::{REGISTRY, RegistryExt, TaggedRegistryExt, vanilla_blocks};
9use steel_utils::types::UpdateFlags;
10use steel_utils::{BlockPos, BlockStateId};
11
12use crate::behavior::{BlockBehavior, BlockPlaceContext};
13use crate::block_entity::SharedBlockEntity;
14use crate::chunk::light::LightLayer;
15use crate::player::Player;
16use crate::world::World;
17
18#[block_behavior]
20pub struct IceBlock {
21 block: BlockRef,
22}
23
24pub const BASE_MELT_LIGHT_LEVEL: u8 = 11;
25
26impl IceBlock {
27 #[must_use]
29 pub const fn new(block: BlockRef) -> Self {
30 Self { block }
31 }
32
33 #[must_use]
35 pub fn melts_into() -> BlockStateId {
36 vanilla_blocks::WATER.default_state()
37 }
38
39 pub fn melt(_state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
41 if world.dimension_type.water_evaporates {
42 world.set_block(
43 pos,
44 vanilla_blocks::AIR.default_state(),
45 UpdateFlags::UPDATE_ALL,
46 );
47 } else {
48 world.set_block(pos, Self::melts_into(), UpdateFlags::UPDATE_ALL);
49 world.update_neighbors_at(pos, Self::melts_into().get_block());
50 }
51 }
52
53 #[must_use]
55 pub fn prevents_ice_melting(tool: &ItemStack) -> bool {
56 let Some(enchantments) = tool.get_enchantments() else {
57 return false;
58 };
59 enchantments.iter().any(|(key, _)| {
60 REGISTRY.enchantments.by_key(key).is_some_and(|ench| {
61 REGISTRY
62 .enchantments
63 .is_in_tag(ench, &EnchantmentTag::PREVENTS_ICE_MELTING)
64 })
65 })
66 }
67}
68
69impl BlockBehavior for IceBlock {
70 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
71 Some(self.block.default_state())
72 }
73
74 fn player_destroy(
75 &self,
76 world: &Arc<World>,
77 _player: &Player,
78 pos: BlockPos,
79 _state: BlockStateId,
80 _block_entity: Option<&SharedBlockEntity>,
81 tool: &ItemStack,
82 ) {
83 if !Self::prevents_ice_melting(tool) {
84 if world.dimension_type.water_evaporates {
85 world.set_block(
86 pos,
87 vanilla_blocks::AIR.default_state(),
88 UpdateFlags::UPDATE_ALL,
89 );
90 return;
91 }
92
93 let below_state = world.get_block_state(pos.below());
94 if below_state.blocks_motion()
95 || !below_state.get_fluid_state().is_empty()
96 || below_state.get_block().config.liquid
97 {
98 world.set_block(pos, Self::melts_into(), UpdateFlags::UPDATE_ALL);
99 }
100 }
101 }
102 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
103 if world.light_value_at(LightLayer::Block, pos)
104 > BASE_MELT_LIGHT_LEVEL - state.get_light_dampening()
105 {
106 Self::melt(state, world, pos);
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::IceBlock;
114 use steel_registry::init_vanilla_registry;
115 use steel_registry::item_stack::ItemStack;
116 use steel_registry::{vanilla_blocks, vanilla_enchantments, vanilla_items};
117
118 #[test]
119 fn melts_into_water_by_default() {
120 init_vanilla_registry();
121 assert_eq!(
122 IceBlock::melts_into(),
123 vanilla_blocks::WATER.default_state()
124 );
125 }
126
127 #[test]
128 fn prevents_ice_melting_with_silk_touch() {
129 init_vanilla_registry();
130 let mut tool = ItemStack::new(&vanilla_items::DIAMOND_PICKAXE);
131 assert!(!IceBlock::prevents_ice_melting(&tool));
132
133 tool.upgrade_enchantment(vanilla_enchantments::SILK_TOUCH.key.clone(), 1);
134 assert!(IceBlock::prevents_ice_melting(&tool));
135 }
136}