Skip to main content

ghast_data/v1_8/
mod.rs

1//! Minecraft 1.8.x (protocol 47) data: numeric block ids with 4-bit metadata.
2//!
3//! In 1.8 a block state travels on the wire and in chunk sections as one `u16`:
4//! `block_id << 4 | meta` ([`BlockState::raw`]). Block ids are `0..=197`; every meta `0..=15`
5//! is representable. Collision shapes are per (id, meta); blocks whose shape vanilla computes from
6//! neighbours report a [`ShapeDependency::Neighbours`] instead of pretending to be static.
7//!
8//! Unregistered combinations: vanilla 1.8 reads a raw value that no registered block state
9//! produces (unknown id, or a meta the block never uses, e.g. `stone:7`) as air.
10//! [`BlockState::is_registered`] tells them apart (registered set from the 1.8.9 oracle),
11//! [`BlockState::from_raw_vanilla`] applies vanilla's mapping, and the collision table already
12//! gives unregistered states no boxes.
13
14use crate::common::{
15    Aabb, BiomeInfo, BlockInfo, BlockPhysics, Collision, DigMaterial, EffectInfo, EnchantmentInfo,
16    EntityInfo, EntityKind, Fluid, FluidKind, FoodInfo, ItemInfo, NeighbourShape, PhysicsFlags,
17    ShapeDependency, lookup_id, lookup_name,
18};
19use crate::generated::v1_8 as g;
20
21/// Number of blocks (ids `0..BLOCK_COUNT`).
22pub const BLOCK_COUNT: usize = g::blocks::BLOCKS.len();
23
24/// Identity of the vanilla 1.8 materials that vanilla code compares with `==`.
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub enum MaterialKind {
27    /// `Material.AIR` (only `air`).
28    Air,
29    /// `Material.WATER` (`water`, `flowing_water`).
30    Water,
31    /// `Material.LAVA` (`lava`, `flowing_lava`).
32    Lava,
33    /// `Material.ICE` (only `ice`; `packed_ice` has its own material).
34    Ice,
35    /// Any other material.
36    Other,
37}
38
39/// A block's vanilla 1.8.9 material (flags from the oracle).
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub struct Material {
42    /// Identity, for `== Material.X` checks.
43    pub kind: MaterialKind,
44    /// `Material.blocksMovement()`.
45    pub blocks_movement: bool,
46    /// `Material.isSolid()`.
47    pub solid: bool,
48    /// `Material.isFluid()`.
49    pub fluid: bool,
50    /// `Material.isOpaque()`.
51    pub opaque: bool,
52    /// `Material.isReplaceable()`.
53    pub replaceable: bool,
54}
55
56/// Vanilla 1.8.9 per-block flags, dumped by the oracle (`tests/golden/1.8.9/collision_boxes.jsonl.gz`
57/// `"type":"block"` lines). In 1.8 these are block-level, not per state.
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
59pub struct VanillaProps {
60    /// `Block.isFullCube()` (Yarn): used e.g. by `ClientPlayerEntity.pushOutOfBlocks`.
61    pub full_cube: bool,
62    /// `Block.isNormalBlock()`: material blocks movement and renders as a normal block.
63    pub normal_block: bool,
64    /// The `Block.fullBlock` field.
65    pub full_block: bool,
66    /// `Block.renderAsNormalBlock()`.
67    pub render_as_normal_block: bool,
68    /// `Block.hasCollision()` (the raytrace "collidable" check, not the collision box).
69    pub has_collision: bool,
70    /// The block's material.
71    pub material: Material,
72}
73
74/// What the 1.8.9 crosshair ray test (`Block.rayTrace` via `World.rayTrace`) sees of a block state.
75#[derive(Clone, Copy, Debug, PartialEq)]
76pub struct Selection {
77    /// Bounds after `setBoundingBox(world, pos)` with all neighbours air (block-local). For
78    /// neighbour-dependent blocks this is the isolated shape, see `dependency`.
79    pub bounds: Aabb,
80    /// Same classes as the collision dependency (fences, panes, walls, doors, chests, stairs).
81    pub dependency: ShapeDependency,
82    /// Stairs: vanilla's ray test runs 8 passes over half-cube sub-boxes instead of using `bounds`.
83    pub multi_pass_ray: bool,
84    /// `Block.canCollide(state, false)`: rays skip the block when false (air, fire, fluids, ...).
85    pub can_collide: bool,
86    /// `Block.canCollide(state, true)` (ray tests that stop at fluids).
87    pub can_collide_liquid: bool,
88    /// The block has a collision box (for `ignoreBlockWithoutBoundingBox` ray tests).
89    pub has_collision_box: bool,
90}
91
92/// A 1.8 block type (registry id `0..=197`).
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
94pub struct Block(pub(crate) u8);
95
96impl Block {
97    /// The block with this registry id, if it exists in 1.8.
98    #[must_use]
99    pub const fn from_id(id: u8) -> Option<Block> {
100        if (id as usize) < BLOCK_COUNT {
101            Some(Block(id))
102        } else {
103            None
104        }
105    }
106
107    /// Looks a block up by registry name (`"stone"`, no namespace).
108    #[must_use]
109    pub fn from_name(name: &str) -> Option<Block> {
110        lookup_name(&g::blocks::BLOCK_NAMES, name).map(Block)
111    }
112
113    /// Registry id.
114    #[must_use]
115    pub const fn id(self) -> u8 {
116        self.0
117    }
118
119    /// Static information (names, hardness, drops, physics).
120    #[must_use]
121    pub fn info(self) -> &'static BlockInfo {
122        &g::blocks::BLOCKS[self.0 as usize]
123    }
124
125    /// Registry name.
126    #[must_use]
127    pub fn name(self) -> &'static str {
128        self.info().name
129    }
130
131    /// Physics properties (slipperiness, climbable, ...).
132    #[must_use]
133    pub fn physics(self) -> &'static BlockPhysics {
134        &g::blocks::BLOCKS[self.0 as usize].physics
135    }
136
137    /// Vanilla 1.8.9 shape and material flags.
138    #[must_use]
139    pub fn vanilla(self) -> &'static VanillaProps {
140        &g::blocks::VANILLA_PROPS[self.0 as usize]
141    }
142
143    /// Vanilla material (shorthand for `self.vanilla().material`).
144    #[must_use]
145    pub fn material(self) -> Material {
146        g::blocks::VANILLA_PROPS[self.0 as usize].material
147    }
148
149    /// `Block.isFullCube()` (shorthand for `self.vanilla().full_cube`).
150    #[must_use]
151    pub fn full_cube(self) -> bool {
152        g::blocks::VANILLA_PROPS[self.0 as usize].full_cube
153    }
154
155    /// What the collision shape depends on besides (id, meta).
156    #[must_use]
157    pub fn shape_dependency(self) -> ShapeDependency {
158        g::blocks::BLOCK_SHAPE_DEPENDENCY[self.0 as usize]
159    }
160
161    /// The state with this metadata (`meta & 15`).
162    #[must_use]
163    pub const fn with_meta(self, meta: u8) -> BlockState {
164        BlockState(((self.0 as u16) << 4) | (meta & 15) as u16)
165    }
166
167    /// The state with metadata 0.
168    #[must_use]
169    pub const fn default_state(self) -> BlockState {
170        self.with_meta(0)
171    }
172
173    /// All blocks in id order.
174    pub fn all() -> impl ExactSizeIterator<Item = Block> {
175        (0..BLOCK_COUNT as u8).map(Block)
176    }
177}
178
179/// A 1.8 block state: `block_id << 4 | meta`, exactly the wire / chunk-section value.
180#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
181pub struct BlockState(u16);
182
183impl BlockState {
184    /// Air (raw 0).
185    pub const AIR: BlockState = BlockState(0);
186
187    /// Wraps a raw `id << 4 | meta` value; `None` if the block id is not a 1.8 block.
188    #[must_use]
189    pub const fn from_raw(raw: u16) -> Option<BlockState> {
190        if ((raw >> 4) as usize) < BLOCK_COUNT {
191            Some(BlockState(raw))
192        } else {
193            None
194        }
195    }
196
197    /// Vanilla's chunk-read mapping: unknown block ids and unregistered metas become air.
198    #[must_use]
199    pub fn from_raw_vanilla(raw: u16) -> BlockState {
200        match BlockState::from_raw(raw) {
201            Some(s) if s.is_registered() => s,
202            _ => BlockState::AIR,
203        }
204    }
205
206    /// `true` when vanilla 1.8.9 has a block state for this (id, meta); otherwise it reads as air.
207    #[must_use]
208    pub fn is_registered(self) -> bool {
209        g::blocks::REGISTERED_METAS[(self.0 >> 4) as usize] & (1 << (self.0 & 15)) != 0
210    }
211
212    /// Builds a state from a block and metadata (`meta & 15`).
213    #[must_use]
214    pub const fn new(block: Block, meta: u8) -> BlockState {
215        block.with_meta(meta)
216    }
217
218    /// The raw `id << 4 | meta` value.
219    #[must_use]
220    pub const fn raw(self) -> u16 {
221        self.0
222    }
223
224    /// The block type.
225    #[must_use]
226    pub const fn block(self) -> Block {
227        Block((self.0 >> 4) as u8)
228    }
229
230    /// Metadata `0..=15`.
231    #[must_use]
232    pub const fn meta(self) -> u8 {
233        (self.0 & 15) as u8
234    }
235
236    /// `true` for air.
237    #[must_use]
238    pub const fn is_air(self) -> bool {
239        self.0 >> 4 == 0
240    }
241
242    /// Collision boxes, what else they depend on, and how vanilla places them in the world
243    /// ([`Collision::world_box`]). Hot path: three table lookups.
244    #[must_use]
245    pub fn collision(self) -> Collision {
246        Collision {
247            boxes: g::blocks::SHAPES[g::blocks::STATE_SHAPE[self.0 as usize] as usize],
248            dependency: g::blocks::BLOCK_SHAPE_DEPENDENCY[(self.0 >> 4) as usize],
249            world_coords: g::blocks::WORLD_COORDS
250                [g::blocks::STATE_WORLD_COORDS[self.0 as usize] as usize],
251        }
252    }
253
254    /// Crosshair ray-test data (oracle `selection_boxes.jsonl.gz`).
255    #[must_use]
256    pub fn selection(self) -> Selection {
257        let f = g::blocks::STATE_SELECTION_FLAGS[self.0 as usize];
258        let dependency = g::blocks::BLOCK_SHAPE_DEPENDENCY[(self.0 >> 4) as usize];
259        Selection {
260            bounds: g::blocks::SELECTION_BOUNDS
261                [g::blocks::STATE_SELECTION[self.0 as usize] as usize],
262            dependency,
263            multi_pass_ray: dependency == ShapeDependency::Neighbours(NeighbourShape::Stairs),
264            can_collide: f & 1 != 0,
265            can_collide_liquid: f & 2 != 0,
266            has_collision_box: f & 4 != 0,
267        }
268    }
269
270    /// The collision boxes from the table (for dependent shapes: the isolated / default shape).
271    #[must_use]
272    pub fn shape(self) -> &'static [Aabb] {
273        g::blocks::SHAPES[g::blocks::STATE_SHAPE[self.0 as usize] as usize]
274    }
275
276    /// Fluid in this state (water / lava blocks; `level` = meta).
277    #[must_use]
278    pub fn fluid(self) -> Option<Fluid> {
279        let flags = self.block().physics().flags;
280        let kind = if flags.contains(PhysicsFlags::WATER) {
281            FluidKind::Water
282        } else if flags.contains(PhysicsFlags::LAVA) {
283            FluidKind::Lava
284        } else {
285            return None;
286        };
287        Some(Fluid {
288            kind,
289            level: self.meta(),
290        })
291    }
292}
293
294/// A 1.8.9 crafting recipe (from the oracle's dump of `RecipeDispatcher.getAllRecipes()`).
295///
296/// Ingredients are `(item id, data)`; data `32767` matches any damage value (vanilla's wildcard,
297/// used for block ingredients). Outputs are `(item id, count, damage)`; no output carries NBT.
298#[derive(Clone, Copy, Debug, PartialEq, Eq)]
299pub enum Recipe {
300    /// Shaped recipe: `inputs` is row-major, `width * height` slots, `None` = must be empty.
301    Shaped {
302        /// Pattern width.
303        width: u8,
304        /// Pattern height.
305        height: u8,
306        /// Row-major ingredients.
307        inputs: &'static [Option<(u16, u16)>],
308        /// Result `(id, count, damage)`.
309        output: (u16, u8, u16),
310    },
311    /// Shapeless recipe: every input must be matched by exactly one grid item.
312    Shapeless {
313        /// Ingredients in vanilla's list order.
314        inputs: &'static [(u16, u16)],
315        /// Result `(id, count, damage)`.
316        output: (u16, u8, u16),
317    },
318    /// A recipe implemented in code.
319    Special(SpecialRecipe),
320}
321
322/// Vanilla 1.8.9 code-defined recipes.
323#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
324pub enum SpecialRecipe {
325    /// `ArmorDyeRecipeType`: dye leather armour.
326    ArmorDye,
327    /// `FireworkRecipeType`: rockets, stars, star fades.
328    Firework,
329    /// `PatternRecipeType`: copy a patterned banner onto a blank banner of the same colour.
330    BannerDuplicate,
331    /// `BookCloningRecipeType`: copy a written book.
332    BookCloning,
333    /// `MapCloningRecipeType`: copy a map.
334    MapCloning,
335    /// `MapUpscaleRecipeType`: zoom a map out.
336    MapUpscale,
337    /// `RepairingRecipeType`: combine two damaged tools.
338    Repairing,
339    /// `CopyingRecipeType`: add a pattern to a banner with dye (appends to `BlockEntityTag.Patterns`).
340    BannerPattern,
341}
342
343/// Wildcard ingredient data value (matches any damage).
344pub const RECIPE_ANY_DATA: u16 = 32767;
345
346/// All 1.8.9 crafting recipes in vanilla's match order: the first that matches the grid wins.
347#[must_use]
348pub fn recipes() -> &'static [Recipe] {
349    &g::recipes::RECIPES
350}
351
352/// 1.8.9 slime / magma cube dimensions for metadata size `n` (vanilla `SlimeEntity.setSize`:
353/// `0.51000005F * n` square, eye height `0.625F * height`).
354#[must_use]
355#[allow(clippy::cast_precision_loss)]
356pub fn slime_dimensions(n: i32) -> crate::common::EntityDimensions {
357    let s = 0.510_000_05_f32 * n as f32;
358    crate::common::EntityDimensions {
359        width: s,
360        height: s,
361        eye_height: 0.625 * s,
362    }
363}
364
365crate::macros::common_api!();