Skip to main content

ghast_data/
common.rs

1//! Version-independent data types shared by every version module.
2//!
3//! The generated per-version tables (`crate::v1_8`, `crate::v1_16_5`) are built from these types.
4//! Everything here is plain `Copy` data or `&'static` slices so the tables can live in `static`
5//! memory with no runtime initialisation.
6
7/// Axis-aligned box in block-local coordinates (`0.0..=1.0` for a full cube; some shapes such as
8/// fences or piston arms extend outside that range).
9///
10/// Field names follow Yarn's `Box` (`minX` .. `maxZ`). Values are exact `f64` copies of the source
11/// data; every value in the shipped tables is a dyadic fraction, so it is also exactly the `f64`
12/// produced by vanilla's `(double)someFloat` bounds.
13#[derive(Clone, Copy, Debug, PartialEq)]
14pub struct Aabb {
15    /// Minimum X (west edge).
16    pub min_x: f64,
17    /// Minimum Y (bottom).
18    pub min_y: f64,
19    /// Minimum Z (north edge).
20    pub min_z: f64,
21    /// Maximum X (east edge).
22    pub max_x: f64,
23    /// Maximum Y (top).
24    pub max_y: f64,
25    /// Maximum Z (south edge).
26    pub max_z: f64,
27}
28
29impl Aabb {
30    /// The full unit cube `[0, 1]³`.
31    pub const FULL: Aabb = Aabb::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
32
33    /// Creates a box from its six bounds.
34    #[must_use]
35    pub const fn new(
36        min_x: f64,
37        min_y: f64,
38        min_z: f64,
39        max_x: f64,
40        max_y: f64,
41        max_z: f64,
42    ) -> Self {
43        Aabb {
44            min_x,
45            min_y,
46            min_z,
47            max_x,
48            max_y,
49            max_z,
50        }
51    }
52
53    /// The box translated by a block position (`x`, `y`, `z` are the block's integer coordinates),
54    /// in `double` arithmetic. Use [`Collision::world_box`] for vanilla-exact placement.
55    #[must_use]
56    pub fn at(&self, x: i32, y: i32, z: i32) -> Aabb {
57        let (x, y, z) = (f64::from(x), f64::from(y), f64::from(z));
58        Aabb::new(
59            self.min_x + x,
60            self.min_y + y,
61            self.min_z + z,
62            self.max_x + x,
63            self.max_y + y,
64            self.max_z + z,
65        )
66    }
67}
68
69/// What a block's collision shape depends on besides its own block state.
70///
71/// When this is not [`ShapeDependency::None`], [`Collision::boxes`] is only the shape for the
72/// "default" situation described on each variant and the caller (world / physics crate) must
73/// compute the real shape. The tables never pretend such shapes are static.
74#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
75pub enum ShapeDependency {
76    /// The shape is fully determined by the block state.
77    None,
78    /// The shape depends on neighbouring blocks (1.8 "actual state" blocks). `boxes` is the
79    /// shape with no connecting neighbours, as given by minecraft-data.
80    Neighbours(NeighbourShape),
81    /// The shape depends on the colliding entity (1.16 scaffolding: sneaking / standing above).
82    /// `boxes` is the shape minecraft-data reports.
83    EntityContext,
84    /// The shape depends on a block entity (1.16 shulker box lid, moving piston). `boxes` is the
85    /// shape minecraft-data reports.
86    BlockEntity,
87    /// The shape is shifted by a per-position pseudo-random XZ offset (1.16 bamboo). `boxes` is the
88    /// un-offset shape.
89    PositionOffset,
90}
91
92/// Family of a neighbour-dependent shape. The world/physics crate implements the vanilla
93/// connection rules per family.
94#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
95pub enum NeighbourShape {
96    /// Fences: centre post plus arms towards connecting neighbours, 1.5 high.
97    Fence,
98    /// Glass panes and iron bars.
99    Pane,
100    /// Cobblestone walls (1.5 high, post omitted on straight runs).
101    Wall,
102    /// Stairs: straight / inner / outer corner shapes from adjacent stairs.
103    Stairs,
104    /// Doors: the shape combines the lower half (facing, open) with the upper half (hinge).
105    Door,
106    /// Chests: the box extends to the block edge towards an adjacent chest of the same kind.
107    Chest,
108}
109
110/// How vanilla turns one bound of a collision box into an absolute world coordinate.
111///
112/// Most 1.8 blocks compute `blockPos + localBound` in `double` ([`WorldCoord::Double`]), which is
113/// exact. A few (cactus, cake, fence gate, soul sand, snow layer) do the addition in `float`:
114/// `(float) (blockPos + int_offset) + term`, widened to `double`. That rounds once
115/// `|blockPos|` is large (from 2^20 ≈ 1M blocks for 1/16 steps), and the rounded value is what
116/// vanilla collides with.
117#[derive(Clone, Copy, Debug, PartialEq)]
118pub enum WorldCoord {
119    /// `f64::from(pos) + local` (the matching [`Aabb`] bound).
120    Double,
121    /// `f64::from((pos + int_offset) as f32 + term)`: Java `i2f`, `fadd`, `f2d`.
122    Float {
123        /// Integer added to the block coordinate before the `float` conversion (`pos.getX() + 1`).
124        int_offset: i8,
125        /// `float` term added afterwards (negative for `pos.getX() + 1 - f`).
126        term: f32,
127    },
128}
129
130/// Per-bound world-coordinate rules of one box, in [`Aabb`] field order
131/// (`min_x, min_y, min_z, max_x, max_y, max_z`).
132pub type WorldCoords = [WorldCoord; 6];
133
134impl WorldCoord {
135    /// The absolute coordinate for block coordinate `pos` and local bound `local`.
136    #[inline]
137    #[must_use]
138    pub fn apply(self, pos: i32, local: f64) -> f64 {
139        match self {
140            WorldCoord::Double => f64::from(pos) + local,
141            #[allow(clippy::cast_precision_loss)]
142            WorldCoord::Float { int_offset, term } => {
143                f64::from(pos.wrapping_add(i32::from(int_offset)) as f32 + term)
144            }
145        }
146    }
147}
148
149/// `(min, max)` of two coordinates (never NaN here).
150fn ordered(a: f64, b: f64) -> (f64, f64) {
151    if a <= b { (a, b) } else { (b, a) }
152}
153
154/// The collision of one block state.
155#[derive(Clone, Copy, Debug, PartialEq)]
156pub struct Collision {
157    /// Boxes in block-local coordinates; empty means no collision.
158    pub boxes: &'static [Aabb],
159    /// Whether `boxes` is the final shape or needs more context.
160    pub dependency: ShapeDependency,
161    /// `Some` when vanilla builds these boxes with `float` world coordinates (one rule set per
162    /// box, parallel to `boxes`); `None` = plain `double` addition, i.e. [`Aabb::at`].
163    pub world_coords: Option<&'static [WorldCoords]>,
164}
165
166impl Collision {
167    /// `true` when at least one bound is computed in `float` world coordinates.
168    #[must_use]
169    pub fn uses_float_world_coords(&self) -> bool {
170        self.world_coords.is_some()
171    }
172
173    /// Box `i` placed at block position (`x`, `y`, `z`), bit-exact as vanilla computes it.
174    ///
175    /// # Panics
176    /// If `i >= self.boxes.len()`.
177    #[inline]
178    #[must_use]
179    pub fn world_box(&self, i: usize, x: i32, y: i32, z: i32) -> Aabb {
180        let b = &self.boxes[i];
181        match self.world_coords {
182            None => b.at(x, y, z),
183            Some(rules) => {
184                let r = &rules[i];
185                // Vanilla's Box constructor orders each axis (Math.min / Math.max); float rounding
186                // can invert a thin bound pair far from the origin (cake below x = -2^24).
187                let (x0, x1) = ordered(r[0].apply(x, b.min_x), r[3].apply(x, b.max_x));
188                let (y0, y1) = ordered(r[1].apply(y, b.min_y), r[4].apply(y, b.max_y));
189                let (z0, z1) = ordered(r[2].apply(z, b.min_z), r[5].apply(z, b.max_z));
190                Aabb::new(x0, y0, z0, x1, y1, z1)
191            }
192        }
193    }
194
195    /// All boxes placed at block position (`x`, `y`, `z`), bit-exact as vanilla computes them.
196    pub fn world_boxes(&self, x: i32, y: i32, z: i32) -> impl ExactSizeIterator<Item = Aabb> + '_ {
197        (0..self.boxes.len()).map(move |i| self.world_box(i, x, y, z))
198    }
199    /// `true` when the block state has no collision boxes and nothing else can add one.
200    #[must_use]
201    pub fn is_empty(&self) -> bool {
202        self.boxes.is_empty() && self.dependency == ShapeDependency::None
203    }
204
205    /// `true` when the shape is exactly one full unit cube and does not depend on anything else.
206    #[must_use]
207    pub fn is_full_cube(&self) -> bool {
208        self.dependency == ShapeDependency::None && self.boxes == [Aabb::FULL]
209    }
210}
211
212/// minecraft-data's coarse bounding-box classification.
213#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
214pub enum BoundingBox {
215    /// No collision ("empty").
216    Empty,
217    /// Has some collision ("block"); see the block state's [`Collision`] for the real shape.
218    Block,
219}
220
221/// minecraft-data's harvest "material" group: selects the tool speed multipliers
222/// (see the per-version `tool_multiplier`). This is NOT vanilla's `Material` class.
223#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
224pub enum DigMaterial {
225    /// Pickaxe group.
226    Rock,
227    /// Axe group.
228    Wood,
229    /// Axe/sword group (plants).
230    Plant,
231    /// Sword-only group (melons, pumpkins).
232    Melon,
233    /// Leaves (sword, shears).
234    Leaves,
235    /// Shovel group.
236    Dirt,
237    /// Cobweb (sword, shears).
238    Web,
239    /// Wool (shears).
240    Wool,
241    /// 1.16 data's explicit "default" group (no tool bonus).
242    Default,
243}
244
245/// A block's item drop as listed by minecraft-data (no loot-table logic, no fortune).
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub struct Drop {
248    /// Item id.
249    pub item: u16,
250    /// Item metadata / damage (1.8 only); `None` = 0 / unspecified.
251    pub meta: Option<u8>,
252    /// Minimum count.
253    pub min: u8,
254    /// Maximum count.
255    pub max: u8,
256}
257
258/// Bit set of physics-relevant block flags.
259#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
260pub struct PhysicsFlags(pub u16);
261
262impl PhysicsFlags {
263    /// No flags.
264    pub const NONE: PhysicsFlags = PhysicsFlags(0);
265    /// The entity can climb while inside this block (ladder, vine, ...).
266    pub const CLIMBABLE: PhysicsFlags = PhysicsFlags(1 << 0);
267    /// The block is a water block (still / flowing). In 1.16 waterlogged states are reported by the
268    /// per-state fluid table instead.
269    pub const WATER: PhysicsFlags = PhysicsFlags(1 << 1);
270    /// The block is a lava block (still / flowing).
271    pub const LAVA: PhysicsFlags = PhysicsFlags(1 << 2);
272    /// Entities inside are slowed by the block's `stuck_multiplier` (cobweb, sweet berry bush).
273    pub const STUCK: PhysicsFlags = PhysicsFlags(1 << 3);
274    /// Slime block: bounces on landing and slows walking.
275    pub const SLIME: PhysicsFlags = PhysicsFlags(1 << 4);
276    /// Bed: bounces on landing (1.12+ behaviour; never set for 1.8).
277    pub const BED_BOUNCE: PhysicsFlags = PhysicsFlags(1 << 5);
278    /// Honey block: wall sliding (1.15+).
279    pub const HONEY: PhysicsFlags = PhysicsFlags(1 << 6);
280    /// Bubble column (1.13+): pushes entities up or down.
281    pub const BUBBLE_COLUMN: PhysicsFlags = PhysicsFlags(1 << 7);
282    /// 1.8 soul sand: `speed_factor` is applied to horizontal motion when the entity's box
283    /// intersects the block (vanilla `Block.onEntityCollision`), not as a ground speed factor.
284    pub const COLLIDE_SLOWDOWN: PhysicsFlags = PhysicsFlags(1 << 8);
285    /// Fence: 1.16 `#minecraft:fences` tag; 1.8 `FenceBlock` (wooden + nether brick fences).
286    pub const FENCE: PhysicsFlags = PhysicsFlags(1 << 9);
287    /// Wall: 1.16 `#minecraft:walls` tag; 1.8 `WallBlock` (cobblestone wall).
288    pub const WALL: PhysicsFlags = PhysicsFlags(1 << 10);
289    /// Fence gate: 1.16 `#minecraft:fence_gates` tag; 1.8 `FenceGateBlock`.
290    pub const FENCE_GATE: PhysicsFlags = PhysicsFlags(1 << 11);
291    /// Trapdoor: 1.16 `#minecraft:trapdoors` tag; 1.8 `TrapdoorBlock`.
292    pub const TRAPDOOR: PhysicsFlags = PhysicsFlags(1 << 12);
293    /// 1.16 `#minecraft:soul_speed_blocks` (soul sand, soul soil); never set for 1.8.
294    pub const SOUL_SPEED: PhysicsFlags = PhysicsFlags(1 << 13);
295    /// 1.16 `#minecraft:fire` (fire, soul fire); never set for 1.8.
296    pub const FIRE: PhysicsFlags = PhysicsFlags(1 << 14);
297
298    /// `true` when every flag of `other` is set in `self`.
299    #[must_use]
300    pub const fn contains(self, other: PhysicsFlags) -> bool {
301        self.0 & other.0 == other.0
302    }
303
304    /// Union of two flag sets.
305    #[must_use]
306    pub const fn union(self, other: PhysicsFlags) -> PhysicsFlags {
307        PhysicsFlags(self.0 | other.0)
308    }
309}
310
311/// Physics properties of a block (vanilla facts that minecraft-data does not carry; maintained in
312/// `codegen/data/src/facts.rs`).
313#[derive(Clone, Copy, Debug, PartialEq)]
314pub struct BlockPhysics {
315    /// Ground friction ("slipperiness"), vanilla `float`: default 0.6, ice 0.98, slime 0.8.
316    pub slipperiness: f32,
317    /// Horizontal speed multiplier. The exact `f64` vanilla multiplies by: `0.4` (a `double`
318    /// literal) for 1.8 soul sand, `0.4f32 as f64` for 1.16 speed factors. `1.0` = none.
319    pub speed_factor: f64,
320    /// Jump velocity multiplier (vanilla `float`; 1.16 honey block 0.5). `1.0` = none.
321    pub jump_factor: f32,
322    /// Motion multiplier `[x, y, z]` while inside the block (when [`PhysicsFlags::STUCK`]).
323    pub stuck_multiplier: [f64; 3],
324    /// Flag bits.
325    pub flags: PhysicsFlags,
326}
327
328impl BlockPhysics {
329    /// Properties of an ordinary block.
330    pub const DEFAULT: BlockPhysics = BlockPhysics {
331        slipperiness: 0.6,
332        speed_factor: 1.0,
333        jump_factor: 1.0,
334        stuck_multiplier: [1.0, 1.0, 1.0],
335        flags: PhysicsFlags::NONE,
336    };
337
338    /// The speed factor as the `float` 1.16 `Block.getVelocityMultiplier()` returns (exact: the
339    /// 1.16 tables store `f64::from(f32)`). For 1.8 use [`BlockPhysics::speed_factor`] directly.
340    #[allow(clippy::cast_possible_truncation)]
341    #[must_use]
342    pub const fn velocity_multiplier(&self) -> f32 {
343        self.speed_factor as f32
344    }
345
346    /// Shorthand for `self.flags.contains(flag)`.
347    #[must_use]
348    pub const fn has(&self, flag: PhysicsFlags) -> bool {
349        self.flags.contains(flag)
350    }
351}
352
353/// Static description of a block (cold data: names, digging, light, drops).
354#[derive(Clone, Copy, Debug, PartialEq)]
355pub struct BlockInfo {
356    /// Registry id (1.8: 0..=255, 1.16: 0..763).
357    pub id: u16,
358    /// Registry name without namespace (`"stone"`).
359    pub name: &'static str,
360    /// English display name.
361    pub display_name: &'static str,
362    /// Hardness (vanilla "strength"); `None` when minecraft-data lists `null` (unbreakable in 1.8).
363    /// Note: 1.16 data lists unbreakable blocks (bedrock, barrier, ...) with hardness `0` and
364    /// `diggable == false`; always check [`BlockInfo::diggable`].
365    pub hardness: Option<f32>,
366    /// Blast resistance.
367    pub resistance: f32,
368    /// Max stack size of the block's item.
369    pub stack_size: u8,
370    /// Whether the block can be dug at all.
371    pub diggable: bool,
372    /// minecraft-data "transparent" flag (light passes / not an opaque cube).
373    pub transparent: bool,
374    /// Emitted light level 0..=15.
375    pub emit_light: u8,
376    /// Light absorbed 0..=15.
377    pub filter_light: u8,
378    /// Coarse bounding-box class.
379    pub bounding_box: BoundingBox,
380    /// Tool-speed group; `None` = no tool bonus.
381    pub material: Option<DigMaterial>,
382    /// Item ids that can harvest this block (get drops). Empty = any tool / hand.
383    pub harvest_tools: &'static [u16],
384    /// Item drops (basic list, no loot tables).
385    pub drops: &'static [Drop],
386    /// Physics properties.
387    pub physics: BlockPhysics,
388}
389
390impl BlockInfo {
391    /// `true` when the block needs a specific tool to drop anything.
392    #[must_use]
393    pub fn requires_tool(&self) -> bool {
394        !self.harvest_tools.is_empty()
395    }
396
397    /// `true` when `item` (an item id, or `None` for the empty hand) harvests the block.
398    #[must_use]
399    pub fn can_harvest_with(&self, item: Option<u16>) -> bool {
400        self.harvest_tools.is_empty() || item.is_some_and(|i| self.harvest_tools.contains(&i))
401    }
402}
403
404/// Fluid kind of a block state.
405#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
406pub enum FluidKind {
407    /// Water (incl. 1.16 waterlogged states and always-wet plants / bubble columns).
408    Water,
409    /// Lava.
410    Lava,
411}
412
413/// A fluid occupying a block state.
414#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
415pub struct Fluid {
416    /// Kind of fluid.
417    pub kind: FluidKind,
418    /// Vanilla `level` value (0 = source, 1..=7 flowing, 8..=15 falling). Waterlogged / always-wet
419    /// blocks report 0.
420    pub level: u8,
421}
422
423impl Fluid {
424    /// Vanilla `FluidState` amount 1..=8: source 8, flowing `8 - level`, falling 8
425    /// (`FluidBlock.getFluidState` maps `level >= 8` to falling with amount 8).
426    #[must_use]
427    pub const fn amount(self) -> u8 {
428        if self.level == 0 || self.level >= 8 {
429            8
430        } else {
431            8 - self.level
432        }
433    }
434
435    /// `true` for falling fluid (`level >= 8`).
436    #[must_use]
437    pub const fn falling(self) -> bool {
438        self.level >= 8
439    }
440
441    /// Vanilla `FlowableFluid.getHeight(state)` = `amount / 9F`: the surface height when the block
442    /// above holds no fluid of the same kind (then vanilla uses 1.0).
443    #[must_use]
444    pub fn height(self) -> f32 {
445        f32::from(self.amount()) / 9.0
446    }
447
448    /// `true` for a source / still fluid (`level == 0`, also waterlogged states).
449    #[must_use]
450    pub const fn still(self) -> bool {
451        self.level == 0
452    }
453}
454
455/// An item.
456#[derive(Clone, Copy, Debug, PartialEq, Eq)]
457pub struct ItemInfo {
458    /// Item id.
459    pub id: u16,
460    /// Registry name without namespace.
461    pub name: &'static str,
462    /// English display name.
463    pub display_name: &'static str,
464    /// Max stack size.
465    pub stack_size: u8,
466    /// Durability for damageable items.
467    pub max_durability: Option<u16>,
468    /// 1.8 metadata variants `(meta, display name)`; empty for 1.16.
469    pub variations: &'static [(u8, &'static str)],
470}
471
472/// Broad entity kind from minecraft-data.
473#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
474pub enum EntityKind {
475    /// Living entity ("mob"). In 1.8 these use the mob-spawn packet's id space.
476    Mob,
477    /// Non-living "object" (1.8 spawn-object packet id space: boats, items, arrows, ...).
478    Object,
479    /// Anything else minecraft-data reports (`"UNKNOWN"`, `"player"`, ...).
480    Other,
481}
482
483/// An entity type.
484#[derive(Clone, Copy, Debug, PartialEq)]
485pub struct EntityInfo {
486    /// Network type id (in 1.8 mob and object ids are separate spaces, see [`EntityKind`]).
487    pub id: u16,
488    /// Name as listed by minecraft-data (1.8: legacy CamelCase names such as `"PigZombie"`).
489    pub name: &'static str,
490    /// English display name.
491    pub display_name: &'static str,
492    /// Mob / object.
493    pub kind: EntityKind,
494    /// minecraft-data category (`"Hostile mobs"`, ...).
495    pub category: &'static str,
496    /// Box width; `None` when unknown. From minecraft-data, NOT verified float-exact.
497    pub width: Option<f32>,
498    /// Box height; `None` when unknown. From minecraft-data, NOT verified float-exact.
499    pub height: Option<f32>,
500}
501
502/// Vanilla entity dimensions (`float`s, as the game stores them).
503#[derive(Clone, Copy, Debug, PartialEq)]
504pub struct EntityDimensions {
505    /// Box width (x and z).
506    pub width: f32,
507    /// Box height.
508    pub height: f32,
509    /// Eye height above the feet.
510    pub eye_height: f32,
511}
512
513impl EntityDimensions {
514    /// The bounding box with its feet centre at (`x`, `y`), `z`, bit-exact as vanilla builds it:
515    /// half width in `float`, then `x - (double) halfWidth`, `y + (double) height`, ...
516    #[must_use]
517    pub fn bounding_box(&self, x: f64, y: f64, z: f64) -> Aabb {
518        let half = f64::from(self.width / 2.0);
519        Aabb::new(
520            x - half,
521            y,
522            z - half,
523            x + half,
524            y + f64::from(self.height),
525            z + half,
526        )
527    }
528}
529
530/// When the vanilla client applies a size variant.
531#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
532pub enum VariantApplied {
533    /// The size right after the spawn packet (the `"default"` variant).
534    Spawn,
535    /// Immediately when the entity metadata packet arrives.
536    Metadata,
537    /// In the entity's next client tick (passive babies, 1.8 wither skeleton, armor stand marker).
538    NextTick,
539    /// From the client player's own input (1.8 sneaking eye height).
540    Input,
541    /// Only on the server: the client never applies it (e.g. 1.8 baby zombie's 0.3 x 0.975); listed
542    /// for reference.
543    Server,
544}
545
546/// One measured size variant of an entity type.
547#[derive(Clone, Copy, Debug, PartialEq)]
548pub struct EntityVariant {
549    /// `"default"`, `"baby"`, `"baby_server"`, `"wither"`, `"elder"`, `"size_1"`..`"size_4"`,
550    /// `"small"`, `"marker"`, `"small_marker"`, `"sneaking"`, `"villager"`.
551    pub name: &'static str,
552    /// The dimensions.
553    pub dimensions: EntityDimensions,
554    /// When the client applies them.
555    pub applied: VariantApplied,
556}
557
558/// Vanilla sizes of one entity type, measured by the oracle on the client, spawned the way the
559/// client spawns it from its packet, then metadata-dependent variants applied like the client does
560/// (`oracle/README.md`, "entity_sizes.jsonl.gz").
561///
562/// Boxes: [`EntityDimensions::bounding_box`] is the centred box vanilla builds on spawn and every
563/// position update. Hanging entities (paintings, item frames) build their box from facing and motive
564/// instead, and vanilla's resize keeps the old min corner until the next position update.
565#[derive(Clone, Copy, Debug, PartialEq)]
566pub struct VanillaEntitySize {
567    /// Name: 1.8 legacy registry name (`"Zombie"`, `"PigZombie"`, `"Player"`), 1.16 registry path
568    /// (`"zombie"`, `"player"`).
569    pub name: &'static str,
570    /// Vanilla registry id (1.8 `EntityType` id = the spawn-mob type for mobs; 1.16 registry id);
571    /// `-1` when the type has none (1.8 players, lightning, fishing bobber) or no client instance.
572    pub registry_id: i32,
573    /// 1.8 spawn packet kind: `"mob"`, `"object"`, `"xp_orb"`, `"painting"`, `"global"`,
574    /// `"player"`; empty for 1.16.
575    pub spawn: &'static str,
576    /// 1.8 spawn-object packet `(type, data)` pairs that create this entity (minecart kinds share
577    /// type 10 with different data).
578    pub object_types: &'static [(i32, i32)],
579    /// Extra growth of the box when picking entities with the crosshair (`getTargetingMargin`).
580    pub targeting_margin: f32,
581    /// 1.16 `EntityDimensions.fixed` (not scaled for babies); `false` in 1.8.
582    pub fixed: bool,
583    /// 1.16 registered type dimensions `(width, height)`; `None` in 1.8.
584    pub registered: Option<(f32, f32)>,
585    /// Measured variants, `"default"` first.
586    pub variants: &'static [EntityVariant],
587    /// 1.16 player: dimensions per pose (`"standing"`, `"crouching"`, `"swimming"`, ...).
588    pub poses: &'static [(&'static str, EntityDimensions)],
589}
590
591impl VanillaEntitySize {
592    /// A measured variant by name.
593    #[must_use]
594    pub fn variant(&self, name: &str) -> Option<&'static EntityVariant> {
595        self.variants.iter().find(|v| v.name == name)
596    }
597
598    /// The size right after spawning (`None` for 1.16 types without a client instance; use
599    /// [`VanillaEntitySize::registered`]).
600    #[must_use]
601    pub fn default_dimensions(&self) -> Option<EntityDimensions> {
602        self.variant("default").map(|v| v.dimensions)
603    }
604
605    /// The `"baby"` variant as the client applies it, for types that have one.
606    #[must_use]
607    pub fn baby(&self) -> Option<EntityDimensions> {
608        self.variant("baby").map(|v| v.dimensions)
609    }
610
611    /// 1.16 player dimensions for a pose.
612    #[must_use]
613    pub fn pose(&self, name: &str) -> Option<EntityDimensions> {
614        self.poses.iter().find(|(n, _)| *n == name).map(|(_, d)| *d)
615    }
616}
617
618/// A biome.
619#[derive(Clone, Copy, Debug, PartialEq)]
620pub struct BiomeInfo {
621    /// Biome id.
622    pub id: u16,
623    /// Registry name.
624    pub name: &'static str,
625    /// English display name.
626    pub display_name: &'static str,
627    /// Category (`"ocean"`, `"plains"`, ...).
628    pub category: &'static str,
629    /// Temperature.
630    pub temperature: f32,
631    /// Rainfall / downfall.
632    pub rainfall: f32,
633    /// Precipitation (`"rain"`, `"snow"`, `"none"`).
634    pub precipitation: &'static str,
635    /// Dimension (`"overworld"`, `"nether"`, `"end"`).
636    pub dimension: &'static str,
637}
638
639/// Linear enchanting cost `a * level + b`.
640#[derive(Clone, Copy, Debug, PartialEq, Eq)]
641pub struct EnchantCost {
642    /// Per-level factor.
643    pub a: i16,
644    /// Offset.
645    pub b: i16,
646}
647
648/// An enchantment.
649#[derive(Clone, Copy, Debug, PartialEq, Eq)]
650pub struct EnchantmentInfo {
651    /// Enchantment id (1.8: the NBT `id` short; 1.16: registry id).
652    pub id: u16,
653    /// Registry name.
654    pub name: &'static str,
655    /// English display name.
656    pub display_name: &'static str,
657    /// Highest level.
658    pub max_level: u8,
659    /// Minimum enchanting cost.
660    pub min_cost: EnchantCost,
661    /// Maximum enchanting cost.
662    pub max_cost: EnchantCost,
663    /// Names of incompatible enchantments.
664    pub exclude: &'static [&'static str],
665    /// Applicable item category (`"armor"`, `"digger"`, ...).
666    pub category: &'static str,
667    /// Random-selection weight.
668    pub weight: u8,
669    /// Only from loot / trades.
670    pub treasure_only: bool,
671    /// Curse.
672    pub curse: bool,
673}
674
675/// A food item.
676#[derive(Clone, Copy, Debug, PartialEq)]
677pub struct FoodInfo {
678    /// Item id (resolved by name against this version's item table).
679    pub item: u16,
680    /// Item name.
681    pub name: &'static str,
682    /// Hunger points restored.
683    pub food_points: u8,
684    /// Saturation restored.
685    pub saturation: f32,
686}
687
688/// A status effect.
689#[derive(Clone, Copy, Debug, PartialEq, Eq)]
690pub struct EffectInfo {
691    /// Effect id.
692    pub id: u8,
693    /// Name as listed by minecraft-data (`"Speed"`).
694    pub name: &'static str,
695    /// English display name.
696    pub display_name: &'static str,
697    /// `true` for beneficial effects.
698    pub good: bool,
699}
700
701/// Binary search in a name-sorted `(name, id)` index.
702#[cfg_attr(not(any(feature = "v1_8", feature = "v1_16_5")), allow(dead_code))]
703pub(crate) fn lookup_name<T: Copy>(index: &[(&'static str, T)], name: &str) -> Option<T> {
704    index
705        .binary_search_by(|(n, _)| (*n).cmp(name))
706        .ok()
707        .map(|i| index[i].1)
708}
709
710/// Binary search in an id-sorted slice by a key function.
711#[cfg_attr(not(any(feature = "v1_8", feature = "v1_16_5")), allow(dead_code))]
712pub(crate) fn lookup_id<T, K: Ord>(
713    items: &'static [T],
714    key: K,
715    f: impl Fn(&T) -> K,
716) -> Option<&'static T> {
717    items
718        .binary_search_by(|x| f(x).cmp(&key))
719        .ok()
720        .map(|i| &items[i])
721}
722
723/// Looks a tool's multiplier up in a generated `TOOL_MULTIPLIERS` table.
724#[cfg_attr(not(any(feature = "v1_8", feature = "v1_16_5")), allow(dead_code))]
725pub(crate) fn tool_multiplier(
726    table: &[(DigMaterial, &[(u16, f32)])],
727    material: DigMaterial,
728    tool: u16,
729) -> f32 {
730    table
731        .iter()
732        .find(|(m, _)| *m == material)
733        .and_then(|(_, tools)| tools.iter().find(|(t, _)| *t == tool))
734        .map_or(1.0, |(_, x)| *x)
735}