mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
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:
File diff suppressed because one or more lines are too long
@@ -1,5 +1,4 @@
|
|||||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3};
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
|
||||||
|
|
||||||
use crate::block_properties::{COLLISION_SHAPES, NoteblockInstrument};
|
use crate::block_properties::{COLLISION_SHAPES, NoteblockInstrument};
|
||||||
use crate::{Block, BlockDirection, BlockId};
|
use crate::{Block, BlockDirection, BlockId};
|
||||||
@@ -178,6 +177,16 @@ impl BlockState {
|
|||||||
.map(|&id| COLLISION_SHAPES[id as usize])
|
.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> + '_ {
|
pub fn get_block_outline_shapes(&self) -> impl Iterator<Item = BoundingBox> + '_ {
|
||||||
let base_shapes = self
|
let base_shapes = self
|
||||||
.outline_shapes
|
.outline_shapes
|
||||||
@@ -190,6 +199,24 @@ impl BlockState {
|
|||||||
|
|
||||||
base_shapes.chain(water_shape)
|
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 {
|
impl BlockStateId {
|
||||||
@@ -281,3 +308,61 @@ const WEST_SIDE_SOLID: u8 = 1 << 4;
|
|||||||
const EAST_SIDE_SOLID: u8 = 1 << 5;
|
const EAST_SIDE_SOLID: u8 = 1 << 5;
|
||||||
const DOWN_CENTER_SOLID: u8 = 1 << 6;
|
const DOWN_CENTER_SOLID: u8 = 1 << 6;
|
||||||
const UP_CENTER_SOLID: u8 = 1 << 7;
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
loot_table::LootTable,
|
loot_table::LootTable,
|
||||||
math::experience::Experience,
|
math::{experience::Experience, position::BlockPos, vector3::Vector3},
|
||||||
|
random::hash_block_pos,
|
||||||
resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation},
|
resource_location::{FromResourceLocation, ResourceLocation, ToResourceLocation},
|
||||||
};
|
};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
@@ -122,6 +123,35 @@ impl FromResourceLocation for &'static Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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]
|
#[must_use]
|
||||||
pub fn is_waterlogged(&self, id: BlockStateId) -> bool {
|
pub fn is_waterlogged(&self, id: BlockStateId) -> bool {
|
||||||
self.properties(id).is_some_and(|properties| {
|
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 {
|
impl BlockId {
|
||||||
// depends on generated impl:
|
// depends on generated impl:
|
||||||
// pub(crate) const BLOCK_COUNT: u16;
|
// pub(crate) const BLOCK_COUNT: u16;
|
||||||
@@ -269,3 +321,78 @@ pub struct Flammable {
|
|||||||
pub spread_chance: u8,
|
pub spread_chance: u8,
|
||||||
pub burn_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
/* This file is generated. Do not edit manually. */
|
/* This file is generated. Do not edit manually. */
|
||||||
use crate::block_state::PistonBehavior;
|
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::*;
|
use pumpkin_util::loot_table::*;
|
||||||
#[allow(
|
#[allow(
|
||||||
clippy::wildcard_imports,
|
clippy::wildcard_imports,
|
||||||
@@ -567411,6 +567414,206 @@ impl Block {
|
|||||||
}),
|
}),
|
||||||
experience: None,
|
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."]
|
#[doc = r" Try to parse a block from a resource location string."]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
@@ -629,7 +629,7 @@ impl BlockRegistry {
|
|||||||
// placement. (e.g. arrows/xp orbs/displays/markers should not)
|
// placement. (e.g. arrows/xp orbs/displays/markers should not)
|
||||||
let state = BlockState::from_id(new_state);
|
let state = BlockState::from_id(new_state);
|
||||||
let mut buildable = true;
|
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);
|
let placed_box = shape.at_pos(final_block_pos);
|
||||||
|
|
||||||
if Self::has_blocking_entity_in_box(world.as_ref(), &placed_box) {
|
if Self::has_blocking_entity_in_box(world.as_ref(), &placed_box) {
|
||||||
|
|||||||
@@ -3035,7 +3035,7 @@ impl Entity {
|
|||||||
for z in blockpos.0.z..=blockpos1.0.z {
|
for z in blockpos.0.z..=blockpos1.0.z {
|
||||||
let pos = BlockPos::new(x, y, z);
|
let pos = BlockPos::new(x, y, z);
|
||||||
let (block, state) = world.get_block_and_state(&pos);
|
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() {
|
if state.outline_shapes.is_empty() {
|
||||||
world
|
world
|
||||||
|
|||||||
@@ -333,11 +333,11 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
|||||||
block_entity_type: state.block_entity_type,
|
block_entity_type: state.block_entity_type,
|
||||||
instrument: to_wit_noteblock_instrument(state.instrument),
|
instrument: to_wit_noteblock_instrument(state.instrument),
|
||||||
collision_shapes: state
|
collision_shapes: state
|
||||||
.get_block_collision_shapes()
|
.get_block_collision_shapes_at(&internal_pos)
|
||||||
.map(to_wit_bounding_box)
|
.map(to_wit_bounding_box)
|
||||||
.collect(),
|
.collect(),
|
||||||
outline_shapes: state
|
outline_shapes: state
|
||||||
.get_block_outline_shapes()
|
.get_block_outline_shapes_at(&internal_pos)
|
||||||
.map(to_wit_bounding_box)
|
.map(to_wit_bounding_box)
|
||||||
.collect(),
|
.collect(),
|
||||||
down_side_solid: state.is_side_solid(InternalBlockDirection::Down),
|
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)
|
.get_block_absolute_y(pos.x as usize, pos.y, pos.z as usize)
|
||||||
.unwrap_or(BlockStateId::AIR);
|
.unwrap_or(BlockStateId::AIR);
|
||||||
let state = id.to_state();
|
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 {
|
Ok(WitBlockState {
|
||||||
id: id.as_u16(),
|
id: id.as_u16(),
|
||||||
@@ -909,11 +910,11 @@ impl pumpkin::plugin::world::HostChunk for PluginHostState {
|
|||||||
block_entity_type: state.block_entity_type,
|
block_entity_type: state.block_entity_type,
|
||||||
instrument: to_wit_noteblock_instrument(state.instrument),
|
instrument: to_wit_noteblock_instrument(state.instrument),
|
||||||
collision_shapes: state
|
collision_shapes: state
|
||||||
.get_block_collision_shapes()
|
.get_block_collision_shapes_at(&world_pos)
|
||||||
.map(to_wit_bounding_box)
|
.map(to_wit_bounding_box)
|
||||||
.collect(),
|
.collect(),
|
||||||
outline_shapes: state
|
outline_shapes: state
|
||||||
.get_block_outline_shapes()
|
.get_block_outline_shapes_at(&world_pos)
|
||||||
.map(to_wit_bounding_box)
|
.map(to_wit_bounding_box)
|
||||||
.collect(),
|
.collect(),
|
||||||
down_side_solid: state.is_side_solid(InternalBlockDirection::Down),
|
down_side_solid: state.is_side_solid(InternalBlockDirection::Down),
|
||||||
|
|||||||
@@ -1693,7 +1693,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut inside = false;
|
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);
|
let outline_shape = shape.at_pos(pos);
|
||||||
|
|
||||||
if outline_shape.intersects(bounding_box) {
|
if outline_shape.intersects(bounding_box) {
|
||||||
@@ -1725,7 +1725,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut shapes = state
|
let mut shapes = state
|
||||||
.get_block_collision_shapes()
|
.get_block_collision_shapes_at(&pos)
|
||||||
.map(|shape| shape.at_pos(pos));
|
.map(|shape| shape.at_pos(pos));
|
||||||
|
|
||||||
if use_collision_shape {
|
if use_collision_shape {
|
||||||
@@ -1779,7 +1779,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
let shape = shape.at_pos(pos);
|
||||||
if shape.intersects(&bounding_box) {
|
if shape.intersects(&bounding_box) {
|
||||||
collided = true;
|
collided = true;
|
||||||
@@ -1817,7 +1817,7 @@ impl World {
|
|||||||
pub fn get_dismount_height(&self, pos: &BlockPos) -> f64 {
|
pub fn get_dismount_height(&self, pos: &BlockPos) -> f64 {
|
||||||
let state = self.get_block_state(pos);
|
let state = self.get_block_state(pos);
|
||||||
let max_y = state
|
let max_y = state
|
||||||
.get_block_collision_shapes()
|
.get_block_collision_shapes_at(pos)
|
||||||
.map(|s| s.max.y)
|
.map(|s| s.max.y)
|
||||||
.fold(f64::NEG_INFINITY, f64::max);
|
.fold(f64::NEG_INFINITY, f64::max);
|
||||||
if max_y != f64::NEG_INFINITY {
|
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 = BlockPos(Vector3::new(pos.0.x, pos.0.y - 1, pos.0.z));
|
||||||
let below_state = self.get_block_state(&below);
|
let below_state = self.get_block_state(&below);
|
||||||
let below_max_y = below_state
|
let below_max_y = below_state
|
||||||
.get_block_collision_shapes()
|
.get_block_collision_shapes_at(&below)
|
||||||
.map(|s| s.max.y)
|
.map(|s| s.max.y)
|
||||||
.fold(f64::NEG_INFINITY, f64::max);
|
.fold(f64::NEG_INFINITY, f64::max);
|
||||||
if below_max_y >= 1.0 {
|
if below_max_y >= 1.0 {
|
||||||
@@ -5662,7 +5662,7 @@ impl World {
|
|||||||
return (true, None);
|
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 {
|
for shape in bounding_boxes {
|
||||||
let world_min = shape.min.add(&block_pos.0.to_f64());
|
let world_min = shape.min.add(&block_pos.0.to_f64());
|
||||||
|
|||||||
@@ -681,6 +681,8 @@ pub struct Block {
|
|||||||
pub states: Vec<BlockState>,
|
pub states: Vec<BlockState>,
|
||||||
/// Experience points dropped when the block is mined, if any.
|
/// Experience points dropped when the block is mined, if any.
|
||||||
pub experience: Option<Experience>,
|
pub experience: Option<Experience>,
|
||||||
|
/// Position-derived shape offset applied by vanilla, if any.
|
||||||
|
shape_offset: Option<BlockShapeOffset>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for Block {
|
impl ToTokens for Block {
|
||||||
@@ -837,6 +839,21 @@ pub struct BlockAssets {
|
|||||||
pub block_entity_types: Vec<String>,
|
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`.
|
/// Reads all block assets and generates the complete block registry `TokenStream`.
|
||||||
pub fn build() -> TokenStream {
|
pub fn build() -> TokenStream {
|
||||||
let be_blocks_data = fs::read("../../assets/bedrock/block_states.nbt").unwrap();
|
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())
|
serde_json::from_str(&fs::read_to_string("../../assets/blocks.json").unwrap())
|
||||||
.expect("Failed to parse blocks.json");
|
.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> =
|
let generated_properties: Vec<GeneratedProperty> =
|
||||||
serde_json::from_str(&fs::read_to_string("../../assets/properties.json").unwrap())
|
serde_json::from_str(&fs::read_to_string("../../assets/properties.json").unwrap())
|
||||||
.expect("Failed to parse properties.json");
|
.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)]
|
#[allow(clippy::wildcard_imports, clippy::enum_glob_use, clippy::too_many_lines, clippy::match_same_arms)]
|
||||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
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 crate::block_state::PistonBehavior;
|
||||||
use pumpkin_util::math::int_provider::{UniformIntProvider, IntProvider, NormalIntProvider};
|
use pumpkin_util::math::int_provider::{UniformIntProvider, IntProvider, NormalIntProvider};
|
||||||
use pumpkin_util::loot_table::*;
|
use pumpkin_util::loot_table::*;
|
||||||
@@ -1251,6 +1294,13 @@ pub fn build() -> TokenStream {
|
|||||||
impl Block {
|
impl Block {
|
||||||
#(#constants_list)*
|
#(#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."]
|
#[doc = r" Try to parse a block from a resource location string."]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
Reference in New Issue
Block a user