steel_core/behavior/blocks/building/
weathering_block.rs1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::{
5 REGISTRY,
6 blocks::{BlockRef, block_state_ext::BlockStateExt},
7};
8use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
9
10use crate::{
11 behavior::{
12 BlockBehavior, BlockPlaceContext,
13 weathering::{get_weather_state, next_copper_stage},
14 },
15 world::World,
16};
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
22#[repr(u8)]
23pub enum WeatherState {
24 Unaffected = 0,
26 Exposed = 1,
28 Weathered = 2,
30 Oxidized = 3,
32}
33
34const SCAN_DISTANCE: i32 = 4;
36
37const BASE_CHANCE: f32 = 0.056_888_89;
40
41pub struct WeatheringCopper {
48 weather_state: WeatherState,
49}
50
51impl WeatheringCopper {
52 #[must_use]
54 pub const fn new(weather_state: WeatherState) -> Self {
55 Self { weather_state }
56 }
57
58 pub fn change_over_time(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
62 if rand::random::<f32>() >= BASE_CHANCE {
63 return;
64 }
65
66 if let Some(next_state) = self.get_next_state(state, world, pos) {
67 world.set_block(pos, next_state, UpdateFlags::UPDATE_ALL);
68 }
69 }
70
71 fn get_next_state(
80 &self,
81 state: BlockStateId,
82 world: &Arc<World>,
83 pos: BlockPos,
84 ) -> Option<BlockStateId> {
85 let own_age = self.weather_state as i32;
86 let mut same_age_count = 0i32;
87 let mut older_count = 0i32;
88
89 for dx in -SCAN_DISTANCE..=SCAN_DISTANCE {
90 for dy in -SCAN_DISTANCE..=SCAN_DISTANCE {
91 for dz in -SCAN_DISTANCE..=SCAN_DISTANCE {
92 if dx.abs() + dy.abs() + dz.abs() > SCAN_DISTANCE {
93 continue;
94 }
95 if dx == 0 && dy == 0 && dz == 0 {
96 continue;
97 }
98
99 let neighbor_pos = BlockPos::new(pos.x() + dx, pos.y() + dy, pos.z() + dz);
100 let neighbor_state = world.get_block_state(neighbor_pos);
101 let neighbor_block = neighbor_state.get_block();
102
103 let Some(neighbor_age) = get_weather_state(neighbor_block) else {
104 continue;
105 };
106 let found_age = neighbor_age as i32;
107
108 if found_age < own_age {
109 return None;
110 }
111
112 if found_age > own_age {
113 older_count += 1;
114 } else {
115 same_age_count += 1;
116 }
117 }
118 }
119 }
120
121 let chance = (older_count + 1) as f32 / (older_count + same_age_count + 1) as f32;
122 let actual_chance = chance * chance * self.get_chance_modifier();
123
124 if rand::random::<f32>() >= actual_chance {
125 return None;
126 }
127
128 let old_block = state.get_block();
129 let new_block = next_copper_stage(old_block)?;
130 Some(REGISTRY.blocks.copy_matching_properties(state, new_block))
131 }
132
133 fn get_chance_modifier(&self) -> f32 {
134 if self.weather_state == WeatherState::Unaffected {
135 0.75
136 } else {
137 1.0
138 }
139 }
140}
141
142#[block_behavior]
146pub struct WeatheringCopperFullBlock {
147 block: BlockRef,
148 #[json_arg(r#enum = "WeatherState", json = "weather_state")]
149 weathering: WeatheringCopper,
150}
151
152impl WeatheringCopperFullBlock {
153 #[must_use]
155 pub const fn new(block: BlockRef, weather_state: WeatherState) -> Self {
156 Self {
157 block,
158 weathering: WeatheringCopper::new(weather_state),
159 }
160 }
161}
162
163impl BlockBehavior for WeatheringCopperFullBlock {
164 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
165 Some(self.block.default_state())
166 }
167
168 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
169 self.weathering.change_over_time(state, world, pos);
170 }
171}