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