1pub use crate::blocks::properties::NoteBlockInstrument;
7use crate::loot_table::LootTableRef;
8use glam::DVec3;
9use steel_utils::{BlockPos, random::get_seed};
10
11use crate::sound_types::SoundType;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum PushReaction {
16 PushPull,
17 Popped,
18 Immoveable,
19 IgnoreEntity,
20 Push,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum OffsetType {
25 None,
26 Xz,
27 Xyz,
28}
29
30#[derive(Debug)]
35pub struct BlockConfig {
36 pub has_collision: bool,
37 pub can_occlude: bool,
38 pub explosion_resistance: f32,
39 pub is_randomly_ticking: bool,
40 pub force_solid_off: bool,
41 pub force_solid_on: bool,
42 pub push_reaction: PushReaction,
43 pub friction: f32,
44 pub speed_factor: f32,
45 pub jump_factor: f32,
46 pub bounce_restitution: f32,
47 pub fall_distance_reduction: f32,
48 pub dynamic_shape: bool,
49 pub offset_type: OffsetType,
50 pub max_horizontal_offset: f32,
51 pub max_vertical_offset: f32,
52 pub destroy_time: f32,
53 pub ignited_by_lava: bool,
54 pub liquid: bool,
55 pub is_air: bool,
56 pub requires_correct_tool_for_drops: bool,
57 pub instrument: NoteBlockInstrument,
58 pub replaceable: bool,
59 pub sound_type: SoundType,
60 pub loot_table: Option<LootTableRef>,
61}
62
63impl BlockConfig {
64 #[must_use]
66 pub const fn new() -> Self {
67 Self {
68 has_collision: true,
69 can_occlude: true,
70 explosion_resistance: 0.0,
71 is_randomly_ticking: false,
72 force_solid_off: false,
73 force_solid_on: false,
74 push_reaction: PushReaction::PushPull,
75 friction: 0.6,
76 speed_factor: 1.0,
77 jump_factor: 1.0,
78 bounce_restitution: 0.0,
79 fall_distance_reduction: 0.0,
80 dynamic_shape: false,
81 offset_type: OffsetType::None,
82 max_horizontal_offset: 0.25,
83 max_vertical_offset: 0.2,
84 destroy_time: 0.0,
85 ignited_by_lava: false,
86 liquid: false,
87 is_air: false,
88 requires_correct_tool_for_drops: false,
89 instrument: NoteBlockInstrument::Harp,
90 replaceable: false,
91 sound_type: crate::sound_types::STONE,
92 loot_table: None,
93 }
94 }
95
96 #[must_use]
97 pub const fn has_collision(mut self, has_collision: bool) -> Self {
98 self.has_collision = has_collision;
99 self
100 }
101
102 #[must_use]
103 pub const fn can_occlude(mut self, can_occlude: bool) -> Self {
104 self.can_occlude = can_occlude;
105 self
106 }
107
108 #[must_use]
109 pub const fn explosion_resistance(mut self, resistance: f32) -> Self {
110 self.explosion_resistance = resistance;
111 self
112 }
113
114 #[must_use]
115 pub const fn set_is_randomly_ticking(mut self, ticking: bool) -> Self {
116 self.is_randomly_ticking = ticking;
117 self
118 }
119
120 #[must_use]
121 pub const fn force_solid_off(mut self, force: bool) -> Self {
122 self.force_solid_off = force;
123 self
124 }
125
126 #[must_use]
127 pub const fn force_solid_on(mut self, force: bool) -> Self {
128 self.force_solid_on = force;
129 self
130 }
131
132 #[must_use]
133 pub const fn push_reaction(mut self, reaction: PushReaction) -> Self {
134 self.push_reaction = reaction;
135 self
136 }
137
138 #[must_use]
139 pub const fn friction(mut self, friction: f32) -> Self {
140 self.friction = friction;
141 self
142 }
143
144 #[must_use]
145 pub const fn speed_factor(mut self, factor: f32) -> Self {
146 self.speed_factor = factor;
147 self
148 }
149
150 #[must_use]
151 pub const fn jump_factor(mut self, factor: f32) -> Self {
152 self.jump_factor = factor;
153 self
154 }
155
156 #[must_use]
157 pub const fn bounce_restitution(mut self, factor: f32) -> Self {
158 self.bounce_restitution = factor;
159 self
160 }
161
162 #[must_use]
163 pub const fn fall_distance_reduction(mut self, factor: f32) -> Self {
164 self.fall_distance_reduction = factor;
165 self
166 }
167
168 #[must_use]
169 pub const fn dynamic_shape(mut self, dynamic: bool) -> Self {
170 self.dynamic_shape = dynamic;
171 self
172 }
173
174 #[must_use]
175 pub const fn offset_type(mut self, offset_type: OffsetType) -> Self {
176 self.offset_type = offset_type;
177 self
178 }
179
180 #[must_use]
181 pub const fn max_horizontal_offset(mut self, offset: f32) -> Self {
182 self.max_horizontal_offset = offset;
183 self
184 }
185
186 #[must_use]
187 pub const fn max_vertical_offset(mut self, offset: f32) -> Self {
188 self.max_vertical_offset = offset;
189 self
190 }
191
192 #[must_use]
193 pub const fn destroy_time(mut self, time: f32) -> Self {
194 self.destroy_time = time;
195 self
196 }
197
198 #[must_use]
199 pub const fn ignited_by_lava(mut self, ignited: bool) -> Self {
200 self.ignited_by_lava = ignited;
201 self
202 }
203
204 #[must_use]
205 pub const fn liquid(mut self, liquid: bool) -> Self {
206 self.liquid = liquid;
207 self
208 }
209
210 #[must_use]
211 pub const fn set_is_air(mut self, is_air: bool) -> Self {
212 self.is_air = is_air;
213 self
214 }
215
216 #[must_use]
217 pub const fn requires_correct_tool_for_drops(mut self, requires: bool) -> Self {
218 self.requires_correct_tool_for_drops = requires;
219 self
220 }
221
222 #[must_use]
223 pub const fn instrument(mut self, instrument: NoteBlockInstrument) -> Self {
224 self.instrument = instrument;
225 self
226 }
227
228 #[must_use]
229 pub const fn replaceable(mut self, replaceable: bool) -> Self {
230 self.replaceable = replaceable;
231 self
232 }
233
234 #[must_use]
235 pub const fn sound_type(mut self, sound_type: SoundType) -> Self {
236 self.sound_type = sound_type;
237 self
238 }
239
240 #[must_use]
241 pub const fn loot_table(mut self, loot_table: Option<LootTableRef>) -> Self {
242 self.loot_table = loot_table;
243 self
244 }
245
246 #[must_use]
248 pub fn offset_at(&self, pos: BlockPos) -> DVec3 {
249 let seed = get_seed(pos.x(), 0, pos.z());
250 let x = horizontal_offset_component(seed & 15, self.max_horizontal_offset);
251 let z = horizontal_offset_component((seed >> 8) & 15, self.max_horizontal_offset);
252
253 match self.offset_type {
254 OffsetType::None => DVec3::ZERO,
255 OffsetType::Xz => DVec3::new(x, 0.0, z),
256 OffsetType::Xyz => {
257 let y = (f64::from(((seed >> 4) & 15) as f32 / 15.0) - 1.0)
258 * f64::from(self.max_vertical_offset);
259 DVec3::new(x, y, z)
260 }
261 }
262 }
263}
264
265impl Default for BlockConfig {
266 fn default() -> Self {
267 Self::new()
268 }
269}
270
271fn horizontal_offset_component(seed_bits: i64, max_horizontal_offset: f32) -> f64 {
272 let raw_offset = (f64::from(seed_bits as f32 / 15.0) - 0.5) * 0.5;
273 raw_offset.clamp(
274 -f64::from(max_horizontal_offset),
275 f64::from(max_horizontal_offset),
276 )
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 fn assert_offset(config: &BlockConfig, pos: BlockPos, expected: DVec3) {
284 let offset = config.offset_at(pos);
285 assert!((offset - expected).length() < 1.0e-7);
286 }
287
288 #[test]
289 fn xz_offset_matches_vanilla_clamping() {
290 let config = BlockConfig::new()
291 .offset_type(OffsetType::Xz)
292 .max_horizontal_offset(0.125);
293
294 assert_offset(&config, BlockPos::ZERO, DVec3::new(-0.125, 0.0, -0.125));
295 assert_offset(
296 &config,
297 BlockPos::new(12, 64, 34),
298 DVec3::new(0.125, 0.0, 0.125),
299 );
300 }
301
302 #[test]
303 fn xz_offset_uses_block_position_seed_without_y() {
304 let config = BlockConfig::new()
305 .offset_type(OffsetType::Xz)
306 .max_horizontal_offset(0.125);
307
308 assert_offset(
309 &config,
310 BlockPos::new(1, 64, 0),
311 DVec3::new(0.116_666_666_666_666_64, 0.0, -0.016_666_666_666_666_663),
312 );
313 assert_offset(
314 &config,
315 BlockPos::new(-5, 12, 7),
316 DVec3::new(-0.049_999_999_999_999_99, 0.0, 0.116_666_666_666_666_64),
317 );
318 }
319
320 #[test]
321 fn xyz_offset_applies_vanilla_vertical_component() {
322 let config = BlockConfig::new()
323 .offset_type(OffsetType::Xyz)
324 .max_horizontal_offset(0.25)
325 .max_vertical_offset(0.1);
326
327 assert_offset(&config, BlockPos::ZERO, DVec3::new(-0.25, -0.1, -0.25));
328 assert_offset(
329 &config,
330 BlockPos::new(3, 40, 9),
331 DVec3::new(
332 -0.049_999_999_999_999_99,
333 -0.066_666_666_666_666_68,
334 -0.049_999_999_999_999_99,
335 ),
336 );
337 }
338}