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::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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user