Skip to main content

ghast_data/v1_12/
mod.rs

1//! Minecraft 1.12.x (protocol 335 – 340) data: numeric block ids with 4-bit metadata.
2//!
3//! As in 1.8, a block state travels on the wire and in chunk palettes as one `u16`:
4//! `block_id << 4 | meta` ([`BlockState::raw`]). Block ids are `0..=255` (253 and 254 are unused).
5//! Every table here comes from the vanilla 1.12.2 oracle (`tests/golden/1.12.2/`): collision boxes
6//! per (id, meta) with air neighbours, the per-state shape flags, block materials, ray-test bounds
7//! and the 1.12 "face shapes" fences, walls and panes connect by.
8//!
9//! Unlike 1.8 there are no history-dependent shapes: 1.12 builds every box from constants and the
10//! block's actual state. Blocks whose actual state depends on neighbours (fences, panes, walls,
11//! stairs, doors, chests, chorus plants) report a [`ShapeDependency::Neighbours`]; their table
12//! shape is the one vanilla has with only air around.
13//!
14//! Unregistered combinations (an unknown id, or a meta the block never produces) read as air, as
15//! vanilla's global palette maps them: [`BlockState::from_raw_vanilla`].
16
17use crate::common::{
18    Aabb, BiomeInfo, BlockInfo, BlockPhysics, Collision, DigMaterial, EffectInfo, EnchantmentInfo,
19    EntityInfo, EntityKind, Fluid, FluidKind, FoodInfo, ItemInfo, NeighbourShape, PhysicsFlags,
20    ShapeDependency, lookup_id, lookup_name,
21};
22use crate::generated::v1_12 as g;
23
24/// Size of the block id space (`0..=255`; not every id is a block, see [`Block::from_id`]).
25pub const BLOCK_ID_SPACE: usize = 256;
26
27/// Identity of the vanilla materials that 1.12 movement code compares with `==`.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub enum MaterialKind {
30    /// `Material.AIR`.
31    Air,
32    /// `Material.WATER`.
33    Water,
34    /// `Material.LAVA`.
35    Lava,
36    /// `Material.ICE` (only `ice`; packed ice and frosted ice have their own).
37    Ice,
38    /// Any other material.
39    Other,
40}
41
42/// A block's vanilla 1.12.2 material (flags from the oracle).
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44pub struct Material {
45    /// Our name for the material (from the Yarn field name, e.g. `"stone"`, `"wood"`).
46    pub name: &'static str,
47    /// Identity, for `== Material.X` checks.
48    pub kind: MaterialKind,
49    /// `Material.blocksMovement()`.
50    pub blocks_movement: bool,
51    /// `Material.isSolid()`.
52    pub solid: bool,
53    /// `Material.isLiquid()`.
54    pub liquid: bool,
55    /// `Material.isOpaque()`.
56    pub opaque: bool,
57    /// `Material.isReplaceable()`.
58    pub replaceable: bool,
59}
60
61/// Per-state shape flags (1.12 moved these from the block to the state).
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
63pub struct StateFlags(pub u8);
64
65impl StateFlags {
66    /// `BlockState.isFullCube()` (Legacy Yarn `method_11730`).
67    pub const FULL_CUBE: StateFlags = StateFlags(1 << 0);
68    /// `BlockState.isNormalCube()` (`method_11734`): opaque material, full cube, no redstone power.
69    /// `ClientPlayerEntity.pushOutOfBlocks` tests it.
70    pub const NORMAL_CUBE: StateFlags = StateFlags(1 << 1);
71    /// `BlockState.isBlockNormalCube()` (`method_11733`): material blocks movement and full cube.
72    pub const BLOCK_NORMAL_CUBE: StateFlags = StateFlags(1 << 2);
73    /// `BlockState.isTopSolid()` (`method_11739`).
74    pub const TOP_SOLID: StateFlags = StateFlags(1 << 3);
75    /// `BlockState.isOpaqueCube()` (`isFullBoundsCubeForCulling`).
76    pub const OPAQUE_CUBE: StateFlags = StateFlags(1 << 4);
77    /// The `Block.fullBlock` field.
78    pub const FULL_BLOCK: StateFlags = StateFlags(1 << 5);
79    /// `BlockState.causesSuffocation()` (`method_13763`).
80    pub const CAUSES_SUFFOCATION: StateFlags = StateFlags(1 << 6);
81
82    /// `true` when every flag of `other` is set.
83    #[must_use]
84    pub const fn contains(self, other: StateFlags) -> bool {
85        self.0 & other.0 == other.0
86    }
87}
88
89/// A block face (`Direction` order: down, up, north, south, west, east).
90#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
91pub enum Face {
92    /// `-Y`.
93    Down,
94    /// `+Y`.
95    Up,
96    /// `-Z`.
97    North,
98    /// `+Z`.
99    South,
100    /// `-X`.
101    West,
102    /// `+X`.
103    East,
104}
105
106/// What a block face looks like to connecting blocks (vanilla 1.12 `BlockFaceShape`; Legacy Yarn
107/// names the enum `BlockRenderLayer`).
108#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
109pub enum FaceShape {
110    /// A full solid face.
111    Solid,
112    /// `BOWL` (cauldron, hopper top).
113    Bowl,
114    /// `CENTER_SMALL`.
115    CenterSmall,
116    /// `MIDDLE_POLE_THIN` (panes, iron bars).
117    MiddlePoleThin,
118    /// `CENTER`.
119    Center,
120    /// `MIDDLE_POLE` (fences, fence gates).
121    MiddlePole,
122    /// `CENTER_BIG`.
123    CenterBig,
124    /// `MIDDLE_POLE_THICK` (walls).
125    MiddlePoleThick,
126    /// `UNDEFINED`.
127    Undefined,
128}
129
130const FACE_SHAPES: [FaceShape; 9] = [
131    FaceShape::Solid,
132    FaceShape::Bowl,
133    FaceShape::CenterSmall,
134    FaceShape::MiddlePoleThin,
135    FaceShape::Center,
136    FaceShape::MiddlePole,
137    FaceShape::CenterBig,
138    FaceShape::MiddlePoleThick,
139    FaceShape::Undefined,
140];
141
142/// What the 1.12.2 crosshair ray test sees of a block state.
143#[derive(Clone, Copy, Debug, PartialEq)]
144pub struct Selection {
145    /// `getBoundingBox(state, world, pos)` with all neighbours air (block-local).
146    pub bounds: Aabb,
147    /// Same classes as the collision dependency.
148    pub dependency: ShapeDependency,
149    /// Stairs: the ray test runs per collision box instead of using `bounds`.
150    pub multi_pass_ray: bool,
151    /// `Block.canCollideCheck(state, false)`.
152    pub can_collide: bool,
153    /// `Block.canCollideCheck(state, true)` (ray tests that stop at fluids).
154    pub can_collide_liquid: bool,
155    /// The state has a collision box.
156    pub has_collision_box: bool,
157}
158
159/// A 1.12 block type (registry id `0..=255`).
160#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
161pub struct Block(pub(crate) u8);
162
163impl Block {
164    /// The block with this registry id, if it exists in 1.12.
165    #[must_use]
166    pub fn from_id(id: u8) -> Option<Block> {
167        (g::blocks::BLOCK_SLOT[id as usize] != 255).then_some(Block(id))
168    }
169
170    /// Looks a block up by registry name (`"stone"`, no namespace; minecraft-data names).
171    #[must_use]
172    pub fn from_name(name: &str) -> Option<Block> {
173        lookup_name(&g::blocks::BLOCK_NAMES, name).map(Block)
174    }
175
176    /// Registry id.
177    #[must_use]
178    pub const fn id(self) -> u8 {
179        self.0
180    }
181
182    /// Static information (names, hardness, drops, physics).
183    #[must_use]
184    pub fn info(self) -> &'static BlockInfo {
185        &g::blocks::BLOCKS[g::blocks::BLOCK_SLOT[self.0 as usize] as usize]
186    }
187
188    /// Registry name.
189    #[must_use]
190    pub fn name(self) -> &'static str {
191        self.info().name
192    }
193
194    /// Physics properties (slipperiness, climbable, ...).
195    #[must_use]
196    pub fn physics(self) -> &'static BlockPhysics {
197        &self.info().physics
198    }
199
200    /// Vanilla material.
201    #[must_use]
202    pub fn material(self) -> Material {
203        g::blocks::MATERIALS[g::blocks::BLOCK_MATERIAL[self.0 as usize] as usize]
204    }
205
206    /// `Block.isCollidable()`: ray tests skip the block when false.
207    #[must_use]
208    pub fn is_collidable(self) -> bool {
209        g::blocks::BLOCK_BITS[self.0 as usize] & 1 != 0
210    }
211
212    /// Fences never connect to this block's solid faces (`FenceBlock`'s exception list: leaves,
213    /// glass, shulker boxes, pistons, barrier, pumpkins, melon, ...).
214    #[must_use]
215    pub fn fence_excluded(self) -> bool {
216        g::blocks::BLOCK_BITS[self.0 as usize] & 2 != 0
217    }
218
219    /// Walls never connect to this block's solid faces.
220    #[must_use]
221    pub fn wall_excluded(self) -> bool {
222        g::blocks::BLOCK_BITS[self.0 as usize] & 4 != 0
223    }
224
225    /// Panes and iron bars never connect to this block's solid faces.
226    #[must_use]
227    pub fn pane_excluded(self) -> bool {
228        g::blocks::BLOCK_BITS[self.0 as usize] & 8 != 0
229    }
230
231    /// What the collision shape depends on besides (id, meta).
232    #[must_use]
233    pub fn shape_dependency(self) -> ShapeDependency {
234        g::blocks::BLOCK_SHAPE_DEPENDENCY[self.0 as usize]
235    }
236
237    /// The state with this metadata (`meta & 15`).
238    #[must_use]
239    pub const fn with_meta(self, meta: u8) -> BlockState {
240        BlockState(((self.0 as u16) << 4) | (meta & 15) as u16)
241    }
242
243    /// The state with metadata 0.
244    #[must_use]
245    pub const fn default_state(self) -> BlockState {
246        self.with_meta(0)
247    }
248
249    /// All blocks in id order.
250    pub fn all() -> impl Iterator<Item = Block> {
251        (0..=255u8).filter_map(Block::from_id)
252    }
253}
254
255/// A 1.12 block state: `block_id << 4 | meta`, exactly the wire / palette value.
256#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
257pub struct BlockState(u16);
258
259impl BlockState {
260    /// Air (raw 0).
261    pub const AIR: BlockState = BlockState(0);
262
263    /// Wraps a raw `id << 4 | meta` value; `None` if the id is not a 1.12 block.
264    #[must_use]
265    pub fn from_raw(raw: u16) -> Option<BlockState> {
266        (raw < 4096 && g::blocks::BLOCK_SLOT[(raw >> 4) as usize] != 255).then_some(BlockState(raw))
267    }
268
269    /// Vanilla's palette mapping: unknown block ids and unregistered metas become air.
270    #[must_use]
271    pub fn from_raw_vanilla(raw: u16) -> BlockState {
272        match BlockState::from_raw(raw) {
273            Some(s) if s.is_registered() => s,
274            _ => BlockState::AIR,
275        }
276    }
277
278    /// `true` when vanilla 1.12.2 has a block state for this (id, meta); otherwise it reads as air.
279    #[must_use]
280    pub fn is_registered(self) -> bool {
281        g::blocks::REGISTERED_METAS[(self.0 >> 4) as usize] & (1 << (self.0 & 15)) != 0
282    }
283
284    /// Builds a state from a block and metadata (`meta & 15`).
285    #[must_use]
286    pub const fn new(block: Block, meta: u8) -> BlockState {
287        block.with_meta(meta)
288    }
289
290    /// The raw `id << 4 | meta` value.
291    #[must_use]
292    pub const fn raw(self) -> u16 {
293        self.0
294    }
295
296    /// The block type.
297    #[must_use]
298    pub const fn block(self) -> Block {
299        Block((self.0 >> 4) as u8)
300    }
301
302    /// Metadata `0..=15`.
303    #[must_use]
304    pub const fn meta(self) -> u8 {
305        (self.0 & 15) as u8
306    }
307
308    /// `true` for air.
309    #[must_use]
310    pub const fn is_air(self) -> bool {
311        self.0 >> 4 == 0
312    }
313
314    /// Collision boxes and what else they depend on (1.12 places them in `double`:
315    /// [`Aabb::at`]).
316    #[must_use]
317    pub fn collision(self) -> Collision {
318        Collision {
319            boxes: self.shape(),
320            dependency: g::blocks::BLOCK_SHAPE_DEPENDENCY[(self.0 >> 4) as usize],
321            world_coords: None,
322        }
323    }
324
325    /// The collision boxes from the table (for dependent shapes: the shape with air neighbours).
326    #[must_use]
327    pub fn shape(self) -> &'static [Aabb] {
328        g::blocks::SHAPES[g::blocks::STATE_SHAPE[self.0 as usize] as usize]
329    }
330
331    /// Per-state shape flags.
332    #[must_use]
333    pub fn flags(self) -> StateFlags {
334        StateFlags(g::blocks::STATE_FLAGS[self.0 as usize])
335    }
336
337    /// `BlockState.isNormalCube()` (see [`StateFlags::NORMAL_CUBE`]).
338    #[must_use]
339    pub fn is_normal_cube(self) -> bool {
340        self.flags().contains(StateFlags::NORMAL_CUBE)
341    }
342
343    /// `BlockState.isFullCube()`.
344    #[must_use]
345    pub fn is_full_cube(self) -> bool {
346        self.flags().contains(StateFlags::FULL_CUBE)
347    }
348
349    /// The face shape of `face` with only air around (stairs: the straight shape; their real
350    /// face shape follows the actual stairs shape).
351    #[must_use]
352    pub fn face_shape(self, face: Face) -> FaceShape {
353        let v = g::blocks::STATE_FACES[self.0 as usize] >> (4 * face as u32);
354        FACE_SHAPES[(v & 15) as usize]
355    }
356
357    /// Crosshair ray-test data (oracle `selection_boxes.jsonl.gz`).
358    #[must_use]
359    pub fn selection(self) -> Selection {
360        let f = g::blocks::STATE_SELECTION_FLAGS[self.0 as usize];
361        let dependency = g::blocks::BLOCK_SHAPE_DEPENDENCY[(self.0 >> 4) as usize];
362        Selection {
363            bounds: g::blocks::SELECTION_BOUNDS
364                [g::blocks::STATE_SELECTION[self.0 as usize] as usize],
365            dependency,
366            multi_pass_ray: dependency == ShapeDependency::Neighbours(NeighbourShape::Stairs),
367            can_collide: f & 1 != 0,
368            can_collide_liquid: f & 2 != 0,
369            has_collision_box: f & 4 != 0,
370        }
371    }
372
373    /// Fluid in this state (water / lava blocks; `level` = meta).
374    #[must_use]
375    pub fn fluid(self) -> Option<Fluid> {
376        let flags = self.block().physics().flags;
377        let kind = if flags.contains(PhysicsFlags::WATER) {
378            FluidKind::Water
379        } else if flags.contains(PhysicsFlags::LAVA) {
380            FluidKind::Lava
381        } else {
382            return None;
383        };
384        Some(Fluid {
385            kind,
386            level: self.meta(),
387        })
388    }
389}
390
391/// 1.12.2 slime / magma cube dimensions for metadata size `n` (`0.51000005F * n` square, eye height
392/// `0.625F * height`), as in 1.8.
393#[must_use]
394#[allow(clippy::cast_precision_loss)]
395pub fn slime_dimensions(n: i32) -> crate::common::EntityDimensions {
396    let s = 0.510_000_05_f32 * n as f32;
397    crate::common::EntityDimensions {
398        width: s,
399        height: s,
400        eye_height: 0.625 * s,
401    }
402}
403
404crate::macros::common_api!();