Skip to main content

ghast_data/v1_16_5/
mod.rs

1//! Minecraft 1.16.5 (protocol 754) data: global block-state palette with properties.
2//!
3//! A [`BlockState`] is the global palette id used on the wire and in chunk sections
4//! (`0..STATE_COUNT`). Each [`Block`] owns a contiguous id range; the state's properties are a
5//! mixed-radix number over that range with the first property most significant (vanilla order).
6//! In 1.16 neighbour connections (fences, panes, walls, stairs, doors, chests) are part of the
7//! state, so their collision shapes are static per state; the few that are not are flagged with a
8//! [`ShapeDependency`].
9
10use 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
20/// Number of blocks (ids `0..BLOCK_COUNT`).
21pub const BLOCK_COUNT: usize = g::blocks::BLOCKS.len();
22/// Number of block states (ids `0..STATE_COUNT`).
23pub const STATE_COUNT: usize = g::blocks::STATE_BLOCK.len();
24
25impl Property {
26    /// Looks a property up by name (`"facing"`).
27    #[must_use]
28    pub fn from_name(name: &str) -> Option<Property> {
29        lookup_name(&g::blocks::PROPERTY_NAMES, name)
30    }
31}
32
33/// One property of a block with its possible values, in palette order.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct PropertyDef {
36    /// Which property.
37    pub property: Property,
38    /// Values in palette order (booleans are `["true", "false"]`, integers `"0"`, `"1"`, ...).
39    pub values: &'static [&'static str],
40}
41
42/// The state-id layout of one block.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub struct StateLayout {
45    /// First state id.
46    pub min_state: u16,
47    /// Number of states.
48    pub state_count: u16,
49    /// Default state id.
50    pub default_state: u16,
51    /// Indices into the property definition table, most significant first.
52    pub(crate) properties: &'static [u8],
53}
54
55/// Vanilla model offset type (`AbstractBlock.OffsetType`): bamboo and plants are shifted by a
56/// per-position pseudo-random offset.
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub enum OffsetType {
59    /// No offset.
60    None,
61    /// Horizontal offset (bamboo's collision shape follows it).
62    Xz,
63    /// Horizontal and vertical offset.
64    Xyz,
65}
66
67/// Vanilla 1.16.5 per-block facts dumped by the oracle.
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub struct VanillaBlock {
70    /// `AbstractBlock.collidable` (false for plants, cobweb, fluids, ...).
71    pub collidable: bool,
72    /// Model / shape offset type.
73    pub offset: OffsetType,
74}
75
76/// Scaffolding's context-dependent collision shapes; pick one with [`ScaffoldingShapes::select`].
77///
78/// Vanilla (`ScaffoldingBlock.getCollisionShape`), confirmed by the oracle's feet-height sweep:
79/// `stable` when the entity's feet `minY > y + 1 - 1.0E-5F` and it is not descending (sneaking);
80/// otherwise `bottom` when the state has `bottom=true`, `distance != 0` and `minY > y + 0 - 1.0E-5F`
81/// (`OUTLINE_SHAPE` is the full cube offset down by one, max Y 0); otherwise empty.
82#[derive(Clone, Copy, Debug, PartialEq)]
83pub struct ScaffoldingShapes {
84    /// The full (stable) shape, also [`BlockState::shape`].
85    pub stable: &'static [Aabb],
86    /// The bottom "collision" shape.
87    pub bottom: &'static [Aabb],
88    /// `stable` as a VoxelShape.
89    pub stable_voxel: &'static VoxelShapeRef,
90    /// `bottom` as a VoxelShape (the empty shape when there is none).
91    pub bottom_voxel: &'static VoxelShapeRef,
92    /// The state has a bottom shape (`bottom=true`, `distance != 0`).
93    pub has_bottom: bool,
94}
95
96impl ScaffoldingShapes {
97    /// The collision shape vanilla uses for an entity whose box bottom is `feet_min_y`, against
98    /// the scaffolding block at height `block_y`. Returns the boxes and the VoxelShape.
99    #[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/// VoxelShape implementation class (decides how a coordinate maps to a cell index in vanilla).
118#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
119pub enum VoxelClass {
120    /// `SimpleVoxelShape`: points are `i / n`; vanilla indexes with `floor(c * n)`, clamped.
121    Simple,
122    /// `ArrayVoxelShape`: explicit point lists; vanilla binary-searches them.
123    Array,
124}
125
126/// A vanilla 1.16.5 collision `VoxelShape` (block-local), exactly as the oracle dumped it.
127#[derive(Clone, Copy, Debug, PartialEq)]
128pub struct VoxelShapeRef {
129    /// Implementation class.
130    pub class: VoxelClass,
131    /// Cell counts `[nx, ny, nz]` (`0, 0, 0` for the empty shape).
132    pub sizes: [u8; 3],
133    /// `getPointPositions(X)` (`nx + 1` values; the empty shape has `[0.0]`).
134    pub xs: &'static [f64],
135    /// `getPointPositions(Y)`.
136    pub ys: &'static [f64],
137    /// `getPointPositions(Z)`.
138    pub zs: &'static [f64],
139    /// Filled-cell bitset, bit index `(x * ny + y) * nz + z`, 64 bits per word.
140    pub cells: &'static [u64],
141    /// `VoxelShape.isEmpty()`.
142    pub empty: bool,
143}
144
145impl VoxelShapeRef {
146    /// `true` when cell (`x`, `y`, `z`) is filled; `false` outside the grid.
147    #[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/// Block side for [`BlockState::side_solid_full_square`].
159#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
160pub enum Side {
161    /// -Z.
162    North = 0,
163    /// +X.
164    East = 1,
165    /// +Z.
166    South = 2,
167    /// -X.
168    West = 3,
169    /// +Y.
170    Up = 4,
171    /// -Y.
172    Down = 5,
173}
174
175/// A 1.16.5 block type (registry id).
176#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
177pub struct Block(pub(crate) u16);
178
179impl Block {
180    /// The block with this registry id.
181    #[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    /// Looks a block up by registry name (`"oak_stairs"`, no namespace).
191    #[must_use]
192    pub fn from_name(name: &str) -> Option<Block> {
193        lookup_name(&g::blocks::BLOCK_NAMES, name).map(Block)
194    }
195
196    /// Registry id.
197    #[must_use]
198    pub const fn id(self) -> u16 {
199        self.0
200    }
201
202    /// Static information (names, hardness, drops, physics).
203    #[must_use]
204    pub fn info(self) -> &'static BlockInfo {
205        &g::blocks::BLOCKS[self.0 as usize]
206    }
207
208    /// Registry name.
209    #[must_use]
210    pub fn name(self) -> &'static str {
211        self.info().name
212    }
213
214    /// Physics properties.
215    #[must_use]
216    pub fn physics(self) -> &'static BlockPhysics {
217        &g::blocks::BLOCKS[self.0 as usize].physics
218    }
219
220    /// What the collision shape depends on besides the state.
221    #[must_use]
222    pub fn shape_dependency(self) -> ShapeDependency {
223        g::blocks::BLOCK_SHAPE_DEPENDENCY[self.0 as usize]
224    }
225
226    /// `true` for `air`, `cave_air` and `void_air` (vanilla `BlockState.isAir`).
227    #[must_use]
228    pub const fn is_air(self) -> bool {
229        matches!(self, Block::AIR | Block::CAVE_AIR | Block::VOID_AIR)
230    }
231
232    /// `true` for the 16 coloured beds (contiguous ids `WHITE_BED..=BLACK_BED`).
233    #[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    /// Vanilla material (Yarn `Material` field), per block as in vanilla.
239    #[must_use]
240    pub fn material(self) -> Material {
241        g::blocks::BLOCK_MATERIAL[self.0 as usize]
242    }
243
244    /// Vanilla per-block facts (oracle).
245    #[must_use]
246    pub fn vanilla(self) -> &'static VanillaBlock {
247        &g::blocks::VANILLA_BLOCKS[self.0 as usize]
248    }
249
250    /// State-id layout.
251    #[must_use]
252    pub fn layout(self) -> &'static StateLayout {
253        &g::blocks::LAYOUTS[self.0 as usize]
254    }
255
256    /// The default state.
257    #[must_use]
258    pub fn default_state(self) -> BlockState {
259        BlockState(self.layout().default_state)
260    }
261
262    /// All states of this block in id order.
263    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    /// The block's properties, most significant first.
269    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    /// The default state with the given properties changed; `None` if a property or value does not
277    /// exist for this block.
278    #[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    /// All blocks in id order.
286    pub fn all() -> impl ExactSizeIterator<Item = Block> {
287        (0..BLOCK_COUNT as u16).map(Block)
288    }
289}
290
291/// A 1.16.5 block state (global palette id).
292#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
293pub struct BlockState(u16);
294
295impl BlockState {
296    /// `minecraft:air` (id 0).
297    pub const AIR: BlockState = BlockState(0);
298
299    /// Wraps a palette id; `None` if out of range.
300    #[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    /// Palette id.
310    #[must_use]
311    pub const fn id(self) -> u16 {
312        self.0
313    }
314
315    /// The block type.
316    #[must_use]
317    pub fn block(self) -> Block {
318        Block(g::blocks::STATE_BLOCK[self.0 as usize])
319    }
320
321    /// `true` for `air`, `cave_air` and `void_air`.
322    #[must_use]
323    pub fn is_air(self) -> bool {
324        self.block().is_air()
325    }
326
327    /// Collision boxes and what else they depend on. Hot path: three table lookups.
328    #[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            // 1.16 places shapes with double arithmetic (VoxelShape offset).
335            world_coords: None,
336        }
337    }
338
339    /// `true` when the collision shape is exactly a full cube (oracle `full_cube`).
340    #[must_use]
341    pub fn full_cube(self) -> bool {
342        g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 1 != 0
343    }
344
345    /// `AbstractBlockState.isSolidBlock` (oracle `solid_block`).
346    #[must_use]
347    pub fn solid_block(self) -> bool {
348        g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 2 != 0
349    }
350
351    /// `AbstractBlockState.shouldSuffocate` (oracle `suffocates`).
352    #[must_use]
353    pub fn should_suffocate(self) -> bool {
354        g::blocks::STATE_VANILLA_FLAGS[self.0 as usize] & 4 != 0
355    }
356
357    /// The collision `VoxelShape` (context-free; see [`BlockState::scaffolding_shapes`]).
358    #[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    /// The collision shape *is* `VoxelShapes.fullCube()` (identity, as vanilla's fast paths test).
368    #[must_use]
369    pub fn full_cube_identity(self) -> bool {
370        self.voxel_flag(1)
371    }
372
373    /// The collision shape *is* `VoxelShapes.empty()` (identity).
374    #[must_use]
375    pub fn empty_identity(self) -> bool {
376        self.voxel_flag(1 << 12)
377    }
378
379    /// `Material.isLiquid()` of the state's material.
380    #[must_use]
381    pub fn material_liquid(self) -> bool {
382        self.voxel_flag(1 << 13)
383    }
384
385    /// `Material.isSolid()` of the state's material.
386    #[must_use]
387    pub fn material_solid(self) -> bool {
388        self.voxel_flag(1 << 14)
389    }
390
391    /// `Material.isReplaceable()` of the state's material.
392    #[must_use]
393    pub fn material_replaceable(self) -> bool {
394        self.voxel_flag(1 << 15)
395    }
396
397    /// `AbstractBlockState.exceedsCube()`.
398    #[must_use]
399    pub fn exceeds_cube(self) -> bool {
400        self.voxel_flag(2)
401    }
402
403    /// `AbstractBlock.dynamicBounds` (no shape cache; shapes recomputed per query).
404    #[must_use]
405    pub fn has_dynamic_bounds(self) -> bool {
406        self.voxel_flag(16)
407    }
408
409    /// `Material.blocksMovement()` of the state's material.
410    #[must_use]
411    pub fn material_blocks_movement(self) -> bool {
412        self.voxel_flag(32)
413    }
414
415    /// `isSideSolidFullSquare(world, pos, side)` in an otherwise empty world.
416    #[must_use]
417    pub fn side_solid_full_square(self, side: Side) -> bool {
418        self.voxel_flag(64 << side as u16)
419    }
420
421    /// Scaffolding context shapes; `None` for other blocks.
422    #[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    /// Collision boxes from the table.
447    #[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    /// Fluid in this state: water / lava blocks with their `level`, waterlogged states and
453    /// always-wet blocks (kelp, seagrass, bubble column) as water level 0.
454    #[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    /// Index of `prop`'s value in [`PropertyDef::values`], if the block has that property.
469    #[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    /// The value of `prop` (`"north"`, `"true"`, `"3"`), if the block has that property.
485    #[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    /// This state with `prop` set to `value`; `None` if the block lacks the property or value.
492    #[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    /// `(property, value)` pairs of this state, most significant property first.
512    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
524/// `minecraft:oak_stairs[facing=north,half=bottom,...]`, the vanilla block-state string form.
525impl 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/// 1.16.5 slime / magma cube dimensions for metadata size `n` (vanilla: the type's 2.04 x 2.04
542/// `scaled(0.255F * n)`, eye height `0.625F * height`).
543#[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!();