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!();