1use crate::common::{
11 Aabb, BiomeInfo, BlockInfo, BlockPhysics, Collision, DigMaterial, EffectInfo, EnchantmentInfo,
12 EntityInfo, EntityKind, Fluid, FluidKind, FoodInfo, ItemInfo, ShapeDependency, lookup_id,
13 lookup_name,
14};
15use crate::generated::v1_16_5 as g;
16use core::fmt;
17
18pub use g::blocks::{Material, Property};
19
20pub const BLOCK_COUNT: usize = g::blocks::BLOCKS.len();
22pub const STATE_COUNT: usize = g::blocks::STATE_BLOCK.len();
24
25impl Property {
26 #[must_use]
28 pub fn from_name(name: &str) -> Option<Property> {
29 lookup_name(&g::blocks::PROPERTY_NAMES, name)
30 }
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct PropertyDef {
36 pub property: Property,
38 pub values: &'static [&'static str],
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub struct StateLayout {
45 pub min_state: u16,
47 pub state_count: u16,
49 pub default_state: u16,
51 pub(crate) properties: &'static [u8],
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub enum OffsetType {
59 None,
61 Xz,
63 Xyz,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub struct VanillaBlock {
70 pub collidable: bool,
72 pub offset: OffsetType,
74}
75
76#[derive(Clone, Copy, Debug, PartialEq)]
83pub struct ScaffoldingShapes {
84 pub stable: &'static [Aabb],
86 pub bottom: &'static [Aabb],
88 pub stable_voxel: &'static VoxelShapeRef,
90 pub bottom_voxel: &'static VoxelShapeRef,
92 pub has_bottom: bool,
94}
95
96impl ScaffoldingShapes {
97 #[must_use]
100 pub fn select(
101 &self,
102 feet_min_y: f64,
103 block_y: i32,
104 descending: bool,
105 ) -> (&'static [Aabb], &'static VoxelShapeRef) {
106 let eps = f64::from(1.0e-5_f32);
107 if feet_min_y > f64::from(block_y) + 1.0 - eps && !descending {
108 (self.stable, self.stable_voxel)
109 } else if self.has_bottom && feet_min_y > f64::from(block_y) + 0.0 - eps {
110 (self.bottom, self.bottom_voxel)
111 } else {
112 (&[], &g::blocks::VOXEL_SHAPES[0])
113 }
114 }
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
119pub enum VoxelClass {
120 Simple,
122 Array,
124}
125
126#[derive(Clone, Copy, Debug, PartialEq)]
128pub struct VoxelShapeRef {
129 pub class: VoxelClass,
131 pub sizes: [u8; 3],
133 pub xs: &'static [f64],
135 pub ys: &'static [f64],
137 pub zs: &'static [f64],
139 pub cells: &'static [u64],
141 pub empty: bool,
143}
144
145impl VoxelShapeRef {
146 #[must_use]
148 pub fn filled(&self, x: usize, y: usize, z: usize) -> bool {
149 let [nx, ny, nz] = self.sizes.map(usize::from);
150 if x >= nx || y >= ny || z >= nz {
151 return false;
152 }
153 let i = (x * ny + y) * nz + z;
154 self.cells[i / 64] & (1 << (i % 64)) != 0
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
160pub enum Side {
161 North = 0,
163 East = 1,
165 South = 2,
167 West = 3,
169 Up = 4,
171 Down = 5,
173}
174
175#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
177pub struct Block(pub(crate) u16);
178
179impl Block {
180 #[must_use]
182 pub const fn from_id(id: u16) -> Option<Block> {
183 if (id as usize) < BLOCK_COUNT {
184 Some(Block(id))
185 } else {
186 None
187 }
188 }
189
190 #[must_use]
192 pub fn from_name(name: &str) -> Option<Block> {
193 lookup_name(&g::blocks::BLOCK_NAMES, name).map(Block)
194 }
195
196 #[must_use]
198 pub const fn id(self) -> u16 {
199 self.0
200 }
201
202 #[must_use]
204 pub fn info(self) -> &'static BlockInfo {
205 &g::blocks::BLOCKS[self.0 as usize]
206 }
207
208 #[must_use]
210 pub fn name(self) -> &'static str {
211 self.info().name
212 }
213
214 #[must_use]
216 pub fn physics(self) -> &'static BlockPhysics {
217 &g::blocks::BLOCKS[self.0 as usize].physics
218 }
219
220 #[must_use]
222 pub fn shape_dependency(self) -> ShapeDependency {
223 g::blocks::BLOCK_SHAPE_DEPENDENCY[self.0 as usize]
224 }
225
226 #[must_use]
228 pub const fn is_air(self) -> bool {
229 matches!(self, Block::AIR | Block::CAVE_AIR | Block::VOID_AIR)
230 }
231
232 #[must_use]
234 pub const fn is_bed(self) -> bool {
235 self.0 >= Block::WHITE_BED.0 && self.0 <= Block::BLACK_BED.0
236 }
237
238 #[must_use]
240 pub fn material(self) -> Material {
241 g::blocks::BLOCK_MATERIAL[self.0 as usize]
242 }
243
244 #[must_use]
246 pub fn vanilla(self) -> &'static VanillaBlock {
247 &g::blocks::VANILLA_BLOCKS[self.0 as usize]
248 }
249
250 #[must_use]
252 pub fn layout(self) -> &'static StateLayout {
253 &g::blocks::LAYOUTS[self.0 as usize]
254 }
255
256 #[must_use]
258 pub fn default_state(self) -> BlockState {
259 BlockState(self.layout().default_state)
260 }
261
262 pub fn states(self) -> impl ExactSizeIterator<Item = BlockState> {
264 let l = self.layout();
265 (l.min_state..l.min_state + l.state_count).map(BlockState)
266 }
267
268 pub fn properties(self) -> impl ExactSizeIterator<Item = &'static PropertyDef> {
270 self.layout()
271 .properties
272 .iter()
273 .map(|&i| &g::blocks::PROPERTY_DEFS[i as usize])
274 }
275
276 #[must_use]
279 pub fn state_with(self, props: &[(Property, &str)]) -> Option<BlockState> {
280 props
281 .iter()
282 .try_fold(self.default_state(), |s, &(p, v)| s.with(p, v))
283 }
284
285 pub fn all() -> impl ExactSizeIterator<Item = Block> {
287 (0..BLOCK_COUNT as u16).map(Block)
288 }
289}
290
291#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
293pub struct BlockState(u16);
294
295impl BlockState {
296 pub const AIR: BlockState = BlockState(0);
298
299 #[must_use]
301 pub const fn from_id(id: u16) -> Option<BlockState> {
302 if (id as usize) < STATE_COUNT {
303 Some(BlockState(id))
304 } else {
305 None
306 }
307 }
308
309 #[must_use]
311 pub const fn id(self) -> u16 {
312 self.0
313 }
314
315 #[must_use]
317 pub fn block(self) -> Block {
318 Block(g::blocks::STATE_BLOCK[self.0 as usize])
319 }
320
321 #[must_use]
323 pub fn is_air(self) -> bool {
324 self.block().is_air()
325 }
326
327 #[must_use]
329 pub fn collision(self) -> Collision {
330 Collision {
331 boxes: self.shape(),
332 dependency: g::blocks::BLOCK_SHAPE_DEPENDENCY
333 [g::blocks::STATE_BLOCK[self.0 as usize] as usize],
334 world_coords: None,
336 }
337 }
338
339 #[must_use]
341 pub fn full_cube(self) -> bool {
342 g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 1 != 0
343 }
344
345 #[must_use]
347 pub fn solid_block(self) -> bool {
348 g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 2 != 0
349 }
350
351 #[must_use]
353 pub fn should_suffocate(self) -> bool {
354 g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 4 != 0
355 }
356
357 #[must_use]
359 pub fn voxel_shape(self) -> &'static VoxelShapeRef {
360 &g::blocks::VOXEL_SHAPES[g::blocks::STATE_VOXEL[self.0 as usize] as usize]
361 }
362
363 fn voxel_flag(self, bit: u16) -> bool {
364 g::blocks::STATE_VOXEL_FLAGS[self.0 as usize] & bit != 0
365 }
366
367 #[must_use]
369 pub fn full_cube_identity(self) -> bool {
370 self.voxel_flag(1)
371 }
372
373 #[must_use]
375 pub fn empty_identity(self) -> bool {
376 self.voxel_flag(1 << 12)
377 }
378
379 #[must_use]
381 pub fn material_liquid(self) -> bool {
382 self.voxel_flag(1 << 13)
383 }
384
385 #[must_use]
387 pub fn material_solid(self) -> bool {
388 self.voxel_flag(1 << 14)
389 }
390
391 #[must_use]
393 pub fn material_replaceable(self) -> bool {
394 self.voxel_flag(1 << 15)
395 }
396
397 #[must_use]
399 pub fn exceeds_cube(self) -> bool {
400 self.voxel_flag(2)
401 }
402
403 #[must_use]
405 pub fn has_dynamic_bounds(self) -> bool {
406 self.voxel_flag(16)
407 }
408
409 #[must_use]
411 pub fn material_blocks_movement(self) -> bool {
412 self.voxel_flag(32)
413 }
414
415 #[must_use]
417 pub fn side_solid_full_square(self, side: Side) -> bool {
418 self.voxel_flag(64 << side as u16)
419 }
420
421 #[must_use]
423 pub fn scaffolding_shapes(self) -> Option<ScaffoldingShapes> {
424 if self.block() != Block::SCAFFOLDING {
425 return None;
426 }
427 let has_bottom =
428 self.get(Property::Bottom) == Some("true") && self.get(Property::Distance) != Some("0");
429 Some(ScaffoldingShapes {
430 stable: self.shape(),
431 bottom: if has_bottom {
432 g::blocks::SCAFFOLDING_BOTTOM
433 } else {
434 &[]
435 },
436 stable_voxel: self.voxel_shape(),
437 has_bottom,
438 bottom_voxel: &g::blocks::VOXEL_SHAPES[if has_bottom {
439 g::blocks::SCAFFOLDING_BOTTOM_VOXEL as usize
440 } else {
441 0
442 }],
443 })
444 }
445
446 #[must_use]
448 pub fn shape(self) -> &'static [Aabb] {
449 g::blocks::SHAPES[g::blocks::STATE_SHAPE[self.0 as usize] as usize]
450 }
451
452 #[must_use]
455 pub fn fluid(self) -> Option<Fluid> {
456 let f = g::blocks::STATE_FLUID[self.0 as usize];
457 let kind = match f >> 4 {
458 1 => FluidKind::Water,
459 2 => FluidKind::Lava,
460 _ => return None,
461 };
462 Some(Fluid {
463 kind,
464 level: f & 15,
465 })
466 }
467
468 #[must_use]
470 pub fn get_index(self, prop: Property) -> Option<usize> {
471 let layout = self.block().layout();
472 let mut rem = usize::from(self.0 - layout.min_state);
473 for &d in layout.properties.iter().rev() {
474 let def = &g::blocks::PROPERTY_DEFS[d as usize];
475 let n = def.values.len();
476 if def.property == prop {
477 return Some(rem % n);
478 }
479 rem /= n;
480 }
481 None
482 }
483
484 #[must_use]
486 pub fn get(self, prop: Property) -> Option<&'static str> {
487 let def = self.property_def(prop)?;
488 self.get_index(prop).map(|i| def.values[i])
489 }
490
491 #[must_use]
493 pub fn with(self, prop: Property, value: &str) -> Option<BlockState> {
494 let layout = self.block().layout();
495 let mut stride = 1usize;
496 for &d in layout.properties.iter().rev() {
497 let def = &g::blocks::PROPERTY_DEFS[d as usize];
498 let n = def.values.len();
499 if def.property == prop {
500 let new = def.values.iter().position(|v| *v == value)?;
501 let base = usize::from(self.0 - layout.min_state);
502 let old = base / stride % n;
503 let id = base - old * stride + new * stride + usize::from(layout.min_state);
504 return Some(BlockState(id as u16));
505 }
506 stride *= n;
507 }
508 None
509 }
510
511 pub fn properties(self) -> impl Iterator<Item = (Property, &'static str)> {
513 let block = self.block();
514 block
515 .properties()
516 .filter_map(move |d| self.get(d.property).map(|v| (d.property, v)))
517 }
518
519 fn property_def(self, prop: Property) -> Option<&'static PropertyDef> {
520 self.block().properties().find(|d| d.property == prop)
521 }
522}
523
524impl fmt::Display for BlockState {
526 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527 write!(f, "minecraft:{}", self.block().name())?;
528 let mut first = true;
529 for (p, v) in self.properties() {
530 f.write_str(if first { "[" } else { "," })?;
531 write!(f, "{}={v}", p.name())?;
532 first = false;
533 }
534 if !first {
535 f.write_str("]")?;
536 }
537 Ok(())
538 }
539}
540
541#[must_use]
544#[allow(clippy::cast_precision_loss)]
545pub fn slime_dimensions(n: i32) -> crate::common::EntityDimensions {
546 let ratio = 0.255_f32 * n as f32;
547 let s = 2.04_f32 * ratio;
548 crate::common::EntityDimensions {
549 width: s,
550 height: s,
551 eye_height: 0.625 * s,
552 }
553}
554
555crate::macros::common_api!();