fix(block): apply vanilla position offsets to block shapes (#2869)

* fix: apply vanilla position offsets to block shapes

* refactor: use extracted block shape offsets
This commit is contained in:
Megalith
2026-08-13 18:06:22 +02:00
committed by GitHub
parent 5a5bc19bbb
commit cb856eb074
9 changed files with 484 additions and 18 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,4 @@
use pumpkin_util::math::boundingbox::BoundingBox;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3};
use crate::block_properties::{COLLISION_SHAPES, NoteblockInstrument};
use crate::{Block, BlockDirection, BlockId};
@@ -178,6 +177,16 @@ impl BlockState {
.map(|&id| COLLISION_SHAPES[id as usize])
}
/// Returns block-local collision shapes with vanilla's coordinate-derived offset applied.
pub fn get_block_collision_shapes_at(
&self,
pos: &BlockPos,
) -> impl Iterator<Item = BoundingBox> + '_ {
let offset = Block::from_state_id(self.id).shape_offset_delta(pos);
self.get_block_collision_shapes()
.map(move |shape| shape.shift(offset))
}
pub fn get_block_outline_shapes(&self) -> impl Iterator<Item = BoundingBox> + '_ {
let base_shapes = self
.outline_shapes
@@ -190,6 +199,24 @@ impl BlockState {
base_shapes.chain(water_shape)
}
/// Returns block-local outline shapes with vanilla's coordinate-derived offset applied.
pub fn get_block_outline_shapes_at(
&self,
pos: &BlockPos,
) -> impl Iterator<Item = BoundingBox> + '_ {
let offset = Block::from_state_id(self.id).shape_offset_delta(pos);
let base_shapes = self
.outline_shapes
.iter()
.map(move |&id| COLLISION_SHAPES[id as usize].shift(offset));
let water_shape = self
.is_waterlogged()
.then(|| BoundingBox::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.875, 1.0)));
base_shapes.chain(water_shape)
}
}
impl BlockStateId {
@@ -281,3 +308,61 @@ const WEST_SIDE_SOLID: u8 = 1 << 4;
const EAST_SIDE_SOLID: u8 = 1 << 5;
const DOWN_CENTER_SOLID: u8 = 1 << 6;
const UP_CENTER_SOLID: u8 = 1 << 7;
#[cfg(test)]
mod tests {
use crate::{Block, BlockStateId, block_state_remap::remap_block_state_for_version};
use pumpkin_util::{math::position::BlockPos, version::JavaMinecraftVersion};
fn assert_close(actual: f64, expected: f64) {
assert!((actual - expected).abs() < 1.0e-6, "{actual} != {expected}");
}
#[test]
fn bamboo_collision_shape_uses_its_world_position() {
let state = Block::BAMBOO.default_state;
let origin_shape = state
.get_block_collision_shapes_at(&BlockPos::new(0, 64, 0))
.next()
.unwrap();
let shifted_shape = state
.get_block_collision_shapes_at(&BlockPos::new(-18, 64, -7))
.next()
.unwrap();
assert_close(origin_shape.min.x, 0.15625);
assert_close(origin_shape.max.x, 0.34375);
assert_close(shifted_shape.min.x, 0.65625);
assert_close(shifted_shape.max.x, 0.84375);
assert_close(shifted_shape.min.z, 0.65625);
assert_close(shifted_shape.max.z, 0.84375);
}
#[test]
fn supported_client_versions_keep_offset_collisions_mapped() {
let versions = [
JavaMinecraftVersion::V_1_20_5,
JavaMinecraftVersion::V_1_21,
JavaMinecraftVersion::V_1_21_2,
JavaMinecraftVersion::V_1_21_4,
JavaMinecraftVersion::V_1_21_5,
JavaMinecraftVersion::V_1_21_6,
JavaMinecraftVersion::V_1_21_7,
JavaMinecraftVersion::V_1_21_9,
JavaMinecraftVersion::V_1_21_11,
JavaMinecraftVersion::V_26_1,
JavaMinecraftVersion::V_26_2,
];
for version in versions {
for block in [Block::BAMBOO, Block::POINTED_DRIPSTONE] {
assert_ne!(
remap_block_state_for_version(block.default_state.id.as_u16(), version),
BlockStateId::AIR.as_u16(),
"{} mapped to air for {version}",
block.name
);
}
}
}
}

View File

@@ -4,7 +4,8 @@ use crate::{
};
use pumpkin_util::{
loot_table::LootTable,
math::experience::Experience,
math::{experience::Experience, position::BlockPos, vector3::Vector3},
random::hash_block_pos,
resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation},
};
use std::hash::{Hash, Hasher};
@@ -122,6 +123,35 @@ impl FromResourceLocation for &'static Block {
}
impl Block {
pub(crate) fn shape_offset_delta(&self, pos: &BlockPos) -> Vector3<f64> {
let Some(shape_offset) = self.shape_offset() else {
return Vector3::new(0.0, 0.0, 0.0);
};
let seed = hash_block_pos(pos.0.x, 0, pos.0.z) as u64;
let max_horizontal = f64::from(shape_offset.max_horizontal);
let x = (f64::from((seed & 15) as f32 / 15.0) - 0.5) * 0.5;
let x = x.clamp(-max_horizontal, max_horizontal);
let z = (f64::from(((seed >> 8) & 15) as f32 / 15.0) - 0.5) * 0.5;
let z = z.clamp(-max_horizontal, max_horizontal);
let y = match shape_offset.offset_type {
ShapeOffsetType::Xz => 0.0,
ShapeOffsetType::Xyz => {
(f64::from(((seed >> 4) & 15) as f32 / 15.0) - 1.0)
* f64::from(shape_offset.max_vertical)
}
};
// Extracted shapes are sampled at BlockPos::ZERO, where vanilla uses the
// negative horizontal limits and, for XYZ offsets, the negative vertical
// limit. Return only the delta from that sample.
Vector3::new(
x + max_horizontal,
y + shape_offset.offset_type.origin_y(shape_offset.max_vertical),
z + max_horizontal,
)
}
#[must_use]
pub fn is_waterlogged(&self, id: BlockStateId) -> bool {
self.properties(id).is_some_and(|properties| {
@@ -196,6 +226,28 @@ impl Block {
}
}
#[derive(Clone, Copy)]
pub(crate) enum ShapeOffsetType {
Xz,
Xyz,
}
#[derive(Clone, Copy)]
pub(crate) struct ShapeOffset {
pub offset_type: ShapeOffsetType,
pub max_horizontal: f32,
pub max_vertical: f32,
}
impl ShapeOffsetType {
fn origin_y(self, max_vertical: f32) -> f64 {
match self {
Self::Xz => 0.0,
Self::Xyz => f64::from(max_vertical),
}
}
}
impl BlockId {
// depends on generated impl:
// pub(crate) const BLOCK_COUNT: u16;
@@ -269,3 +321,78 @@ pub struct Flammable {
pub spread_chance: u8,
pub burn_chance: u8,
}
#[cfg(test)]
mod tests {
use super::{Block, BlockId, ShapeOffsetType};
use pumpkin_util::math::position::BlockPos;
fn assert_close(actual: f64, expected: f64) {
assert!((actual - expected).abs() < 1.0e-6, "{actual} != {expected}");
}
#[test]
fn shape_offset_registry_matches_vanilla_26_2() {
let mut xz = 0;
let mut xyz = 0;
for raw_id in 0..BlockId::BLOCK_COUNT {
match Block::from_id(BlockId::new(raw_id).unwrap())
.shape_offset()
.map(|offset| offset.offset_type)
{
Some(ShapeOffsetType::Xz) => xz += 1,
Some(ShapeOffsetType::Xyz) => xyz += 1,
None => {}
}
}
assert_eq!(xz, 34);
assert_eq!(xyz, 5);
}
#[test]
fn shape_offset_limits_match_vanilla_26_2() {
let bamboo = Block::BAMBOO.shape_offset().unwrap();
assert_eq!(bamboo.max_horizontal, 0.25);
assert_eq!(bamboo.max_vertical, 0.2);
let pointed_dripstone = Block::POINTED_DRIPSTONE.shape_offset().unwrap();
assert_eq!(pointed_dripstone.max_horizontal, 0.125);
let small_dripleaf = Block::SMALL_DRIPLEAF.shape_offset().unwrap();
assert_eq!(small_dripleaf.max_vertical, 0.1);
}
#[test]
fn shape_offset_delta_matches_vanilla_coordinate_hash() {
let origin = BlockPos::new(0, 64, 0);
let positive_extreme = BlockPos::new(-18, 64, -7);
let origin_delta = Block::BAMBOO.shape_offset_delta(&origin);
assert_eq!(origin_delta.x, 0.0);
assert_eq!(origin_delta.y, 0.0);
assert_eq!(origin_delta.z, 0.0);
let bamboo_delta = Block::BAMBOO.shape_offset_delta(&positive_extreme);
assert_eq!(bamboo_delta.x, 0.5);
assert_eq!(bamboo_delta.y, 0.0);
assert_eq!(bamboo_delta.z, 0.5);
let speleothem_delta = Block::POINTED_DRIPSTONE.shape_offset_delta(&positive_extreme);
assert_eq!(speleothem_delta.x, 0.25);
assert_eq!(speleothem_delta.y, 0.0);
assert_eq!(speleothem_delta.z, 0.25);
assert_eq!(
Block::SULFUR_SPIKE.shape_offset_delta(&positive_extreme),
speleothem_delta
);
let xyz_delta = Block::SHORT_GRASS.shape_offset_delta(&positive_extreme);
assert_eq!(xyz_delta.x, 0.5);
assert_close(xyz_delta.y, 0.08);
assert_eq!(xyz_delta.z, 0.5);
assert_eq!(Block::STONE.shape_offset_delta(&positive_extreme).x, 0.0);
}
}

View File

@@ -1,6 +1,9 @@
/* This file is generated. Do not edit manually. */
use crate::block_state::PistonBehavior;
use crate::{Block, BlockId, BlockState, BlockStateId, blocks::Flammable};
use crate::{
Block, BlockId, BlockState, BlockStateId,
blocks::{Flammable, ShapeOffset, ShapeOffsetType},
};
use pumpkin_util::loot_table::*;
#[allow(
clippy::wildcard_imports,
@@ -567411,6 +567414,206 @@ impl Block {
}),
experience: None,
};
pub(crate) const fn shape_offset(&self) -> Option<ShapeOffset> {
match self.id {
BlockId::MANGROVE_PROPAGULE => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::SHORT_GRASS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xyz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::FERN => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xyz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::SHORT_DRY_GRASS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xyz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::TALL_DRY_GRASS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xyz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::TALL_SEAGRASS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::DANDELION => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::GOLDEN_DANDELION => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::TORCHFLOWER => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::POPPY => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::BLUE_ORCHID => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::ALLIUM => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::AZURE_BLUET => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::RED_TULIP => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::ORANGE_TULIP => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::WHITE_TULIP => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::PINK_TULIP => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::OXEYE_DAISY => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::CORNFLOWER => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::WITHER_ROSE => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::LILY_OF_THE_VALLEY => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::SUNFLOWER => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::LILAC => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::ROSE_BUSH => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::PEONY => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::TALL_GRASS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::LARGE_FERN => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::PITCHER_PLANT => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::BAMBOO_SAPLING => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::BAMBOO => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::WARPED_ROOTS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::NETHER_SPROUTS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::CRIMSON_ROOTS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::POINTED_DRIPSTONE => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.125f32,
max_vertical: 0.2f32,
}),
BlockId::SULFUR_SPIKE => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.125f32,
max_vertical: 0.2f32,
}),
BlockId::SMALL_DRIPLEAF => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xyz,
max_horizontal: 0.25f32,
max_vertical: 0.1f32,
}),
BlockId::HANGING_ROOTS => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::OPEN_EYEBLOSSOM => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
BlockId::CLOSED_EYEBLOSSOM => Some(ShapeOffset {
offset_type: ShapeOffsetType::Xz,
max_horizontal: 0.25f32,
max_vertical: 0.2f32,
}),
_ => None,
}
}
#[doc = r" Try to parse a block from a resource location string."]
#[inline]
#[must_use]

View File

@@ -629,7 +629,7 @@ impl BlockRegistry {
// placement. (e.g. arrows/xp orbs/displays/markers should not)
let state = BlockState::from_id(new_state);
let mut buildable = true;
for shape in state.get_block_collision_shapes() {
for shape in state.get_block_collision_shapes_at(&final_block_pos) {
let placed_box = shape.at_pos(final_block_pos);
if Self::has_blocking_entity_in_box(world.as_ref(), &placed_box) {

View File

@@ -3035,7 +3035,7 @@ impl Entity {
for z in blockpos.0.z..=blockpos1.0.z {
let pos = BlockPos::new(x, y, z);
let (block, state) = world.get_block_and_state(&pos);
let block_outlines = state.get_block_outline_shapes();
let block_outlines = state.get_block_outline_shapes_at(&pos);
if state.outline_shapes.is_empty() {
world

View File

@@ -333,11 +333,11 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
block_entity_type: state.block_entity_type,
instrument: to_wit_noteblock_instrument(state.instrument),
collision_shapes: state
.get_block_collision_shapes()
.get_block_collision_shapes_at(&internal_pos)
.map(to_wit_bounding_box)
.collect(),
outline_shapes: state
.get_block_outline_shapes()
.get_block_outline_shapes_at(&internal_pos)
.map(to_wit_bounding_box)
.collect(),
down_side_solid: state.is_side_solid(InternalBlockDirection::Down),
@@ -883,6 +883,7 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
.get_block_absolute_y(pos.x as usize, pos.y, pos.z as usize)
.unwrap_or(BlockStateId::AIR);
let state = id.to_state();
let world_pos = BlockPos::new(chunk_data.x * 16 + pos.x, pos.y, chunk_data.z * 16 + pos.z);
Ok(WitBlockState {
id: id.as_u16(),
@@ -909,11 +910,11 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
block_entity_type: state.block_entity_type,
instrument: to_wit_noteblock_instrument(state.instrument),
collision_shapes: state
.get_block_collision_shapes()
.get_block_collision_shapes_at(&world_pos)
.map(to_wit_bounding_box)
.collect(),
outline_shapes: state
.get_block_outline_shapes()
.get_block_outline_shapes_at(&world_pos)
.map(to_wit_bounding_box)
.collect(),
down_side_solid: state.is_side_solid(InternalBlockDirection::Down),

View File

@@ -1693,7 +1693,7 @@ impl World {
}
let mut inside = false;
'shapes: for shape in state.get_block_outline_shapes() {
'shapes: for shape in state.get_block_outline_shapes_at(&pos) {
let outline_shape = shape.at_pos(pos);
if outline_shape.intersects(bounding_box) {
@@ -1725,7 +1725,7 @@ impl World {
}
let mut shapes = state
.get_block_collision_shapes()
.get_block_collision_shapes_at(&pos)
.map(|shape| shape.at_pos(pos));
if use_collision_shape {
@@ -1779,7 +1779,7 @@ impl World {
}
}
} else {
for shape in state.get_block_collision_shapes() {
for shape in state.get_block_collision_shapes_at(&pos) {
let shape = shape.at_pos(pos);
if shape.intersects(&bounding_box) {
collided = true;
@@ -1817,7 +1817,7 @@ impl World {
pub fn get_dismount_height(&self, pos: &BlockPos) -> f64 {
let state = self.get_block_state(pos);
let max_y = state
.get_block_collision_shapes()
.get_block_collision_shapes_at(pos)
.map(|s| s.max.y)
.fold(f64::NEG_INFINITY, f64::max);
if max_y != f64::NEG_INFINITY {
@@ -1827,7 +1827,7 @@ impl World {
let below = BlockPos(Vector3::new(pos.0.x, pos.0.y - 1, pos.0.z));
let below_state = self.get_block_state(&below);
let below_max_y = below_state
.get_block_collision_shapes()
.get_block_collision_shapes_at(&below)
.map(|s| s.max.y)
.fold(f64::NEG_INFINITY, f64::max);
if below_max_y >= 1.0 {
@@ -5662,7 +5662,7 @@ impl World {
return (true, None);
}
let bounding_boxes = state.get_block_outline_shapes();
let bounding_boxes = state.get_block_outline_shapes_at(block_pos);
for shape in bounding_boxes {
let world_min = shape.min.add(&block_pos.0.to_f64());

View File

@@ -681,6 +681,8 @@ pub struct Block {
pub states: Vec<BlockState>,
/// Experience points dropped when the block is mined, if any.
pub experience: Option<Experience>,
/// Position-derived shape offset applied by vanilla, if any.
shape_offset: Option<BlockShapeOffset>,
}
impl ToTokens for Block {
@@ -837,6 +839,21 @@ pub struct BlockAssets {
pub block_entity_types: Vec<String>,
}
#[derive(Deserialize)]
struct BlockShapeOffset {
#[serde(rename = "type")]
offset_type: BlockShapeOffsetType,
max_horizontal: f32,
max_vertical: f32,
}
#[derive(Clone, Copy, Deserialize)]
#[serde(rename_all = "lowercase")]
enum BlockShapeOffsetType {
Xz,
Xyz,
}
/// Reads all block assets and generates the complete block registry `TokenStream`.
pub fn build() -> TokenStream {
let be_blocks_data = fs::read("../../assets/bedrock/block_states.nbt").unwrap();
@@ -854,6 +871,29 @@ pub fn build() -> TokenStream {
serde_json::from_str(&fs::read_to_string("../../assets/blocks.json").unwrap())
.expect("Failed to parse blocks.json");
let shape_offset_arms = blocks_assets
.blocks
.iter()
.filter_map(|block| {
let offset = block.shape_offset.as_ref()?;
let block = format_ident!("{}", const_block_name_from_block_name(&block.name));
let offset_type = match offset.offset_type {
BlockShapeOffsetType::Xz => quote! { ShapeOffsetType::Xz },
BlockShapeOffsetType::Xyz => quote! { ShapeOffsetType::Xyz },
};
let max_horizontal = offset.max_horizontal;
let max_vertical = offset.max_vertical;
Some(quote! {
BlockId::#block => Some(ShapeOffset {
offset_type: #offset_type,
max_horizontal: #max_horizontal,
max_vertical: #max_vertical,
}),
})
})
.collect::<Vec<_>>();
let generated_properties: Vec<GeneratedProperty> =
serde_json::from_str(&fs::read_to_string("../../assets/properties.json").unwrap())
.expect("Failed to parse properties.json");
@@ -1108,7 +1148,10 @@ pub fn build() -> TokenStream {
#[allow(clippy::wildcard_imports, clippy::enum_glob_use, clippy::too_many_lines, clippy::match_same_arms)]
use pumpkin_util::math::boundingbox::BoundingBox;
use crate::{BlockState, BlockStateId, Block, BlockId, blocks::Flammable};
use crate::{
BlockState, BlockStateId, Block, BlockId,
blocks::{Flammable, ShapeOffset, ShapeOffsetType},
};
use crate::block_state::PistonBehavior;
use pumpkin_util::math::int_provider::{UniformIntProvider, IntProvider, NormalIntProvider};
use pumpkin_util::loot_table::*;
@@ -1251,6 +1294,13 @@ pub fn build() -> TokenStream {
impl Block {
#(#constants_list)*
pub(crate) const fn shape_offset(&self) -> Option<ShapeOffset> {
match self.id {
#(#shape_offset_arms)*
_ => None,
}
}
#[doc = r" Try to parse a block from a resource location string."]
#[inline]
#[must_use]