fix: more fixes

This commit is contained in:
Alexander Medvedev 2026-09-03 22:34:38 +02:00
parent 21e83e9ed9
commit 287404dde5
21 changed files with 29007 additions and 28453 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -126,6 +126,24 @@ impl BlockState {
self.state_flags & HAS_RANDOM_TICKS != 0 self.state_flags & HAS_RANDOM_TICKS != 0
} }
/// Returns whether this block state is a full opaque cube (`isSolidRender()` in Java).
#[must_use]
pub const fn is_solid_render(&self) -> bool {
self.state_flags & IS_SOLID_RENDER != 0
}
/// Returns whether this block state can occlude other blocks (`canOcclude` in Java).
#[must_use]
pub const fn can_occlude(&self) -> bool {
self.state_flags & CAN_OCCLUDE != 0
}
/// Returns whether this block produces an analog output signal for comparators (`hasAnalogOutputSignal()` in Java).
#[must_use]
pub const fn has_analog_output_signal(&self) -> bool {
self.state_flags & HAS_ANALOG_OUTPUT_SIGNAL != 0
}
///`isFaceSturdy()` in Java! ///`isFaceSturdy()` in Java!
#[must_use] #[must_use]
pub const fn is_side_solid(&self, side: BlockDirection) -> bool { pub const fn is_side_solid(&self, side: BlockDirection) -> bool {
@ -275,6 +293,24 @@ impl BlockStateId {
Block::from_state_id(self) Block::from_state_id(self)
} }
#[inline]
#[must_use]
pub const fn is_solid_render(self) -> bool {
self.to_state().is_solid_render()
}
#[inline]
#[must_use]
pub const fn can_occlude(self) -> bool {
self.to_state().can_occlude()
}
#[inline]
#[must_use]
pub const fn has_analog_output_signal(self) -> bool {
self.to_state().has_analog_output_signal()
}
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_waterlogged(self) -> bool { pub fn is_waterlogged(self) -> bool {
@ -324,6 +360,9 @@ const IS_SOLID: u16 = 1 << 6;
const IS_FULL_CUBE: u16 = 1 << 7; const IS_FULL_CUBE: u16 = 1 << 7;
const IS_SOLID_BLOCK: u16 = 1 << 8; const IS_SOLID_BLOCK: u16 = 1 << 8;
const HAS_RANDOM_TICKS: u16 = 1 << 9; const HAS_RANDOM_TICKS: u16 = 1 << 9;
const IS_SOLID_RENDER: u16 = 1 << 10;
const CAN_OCCLUDE: u16 = 1 << 11;
const HAS_ANALOG_OUTPUT_SIGNAL: u16 = 1 << 12;
// side_flags // side_flags
const DOWN_SIDE_SOLID: u8 = 1 << 0; const DOWN_SIDE_SOLID: u8 = 1 << 0;

File diff suppressed because it is too large Load Diff

View File

@ -39,7 +39,9 @@ impl DungeonFeature {
for dy in -1..=4i32 { for dy in -1..=4i32 {
for dz in min_z..=max_z { for dz in min_z..=max_z {
let check_pos = pos.0.add(&Vector3::new(dx, dy, dz)); let check_pos = pos.0.add(&Vector3::new(dx, dy, dz));
let solid = !chunk.is_air(&check_pos); let solid = GenerationCache::get_block_state(chunk, &check_pos)
.to_state()
.is_solid();
if dy == -1 && !solid { if dy == -1 && !solid {
return false; return false;
@ -78,11 +80,13 @@ impl DungeonFeature {
if on_boundary { if on_boundary {
let below_pos = wall_pos.add(&Vector3::new(0, -1, 0)); let below_pos = wall_pos.add(&Vector3::new(0, -1, 0));
let below_solid = !chunk.is_air(&below_pos); let below_solid = GenerationCache::get_block_state(chunk, &below_pos)
let cur_solid = !chunk.is_air(&wall_pos); .to_state()
let is_chest = GenerationCache::get_block_state(chunk, &wall_pos) .is_solid();
.to_block() let cur_state =
== &Block::CHEST; GenerationCache::get_block_state(chunk, &wall_pos).to_state();
let cur_solid = cur_state.is_solid();
let is_chest = cur_state.id.to_block() == &Block::CHEST;
let world_min_y = chunk.bottom_y() as i32; let world_min_y = chunk.bottom_y() as i32;
if wall_pos.y >= world_min_y && !below_solid { if wall_pos.y >= world_min_y && !below_solid {
@ -98,9 +102,9 @@ impl DungeonFeature {
} }
} }
} else { } else {
let state = GenerationCache::get_block_state(chunk, &wall_pos); let state = GenerationCache::get_block_state(chunk, &wall_pos).to_state();
let is_chest = state.to_block() == &Block::CHEST; let is_chest = state.id.to_block() == &Block::CHEST;
let is_spawner = state.to_block() == &Block::SPAWNER; let is_spawner = state.id.to_block() == &Block::SPAWNER;
if !is_chest && !is_spawner { if !is_chest && !is_spawner {
chunk.set_block_state(&wall_pos, Block::CAVE_AIR.default_state); chunk.set_block_state(&wall_pos, Block::CAVE_AIR.default_state);
} }
@ -122,22 +126,20 @@ impl DungeonFeature {
let wall_count = BlockDirection::horizontal() let wall_count = BlockDirection::horizontal()
.iter() .iter()
.filter(|d| !chunk.is_air(&chest_pos.add(&d.to_offset()))) .filter(|d| {
GenerationCache::get_block_state(chunk, &chest_pos.add(&d.to_offset()))
.to_state()
.is_solid()
})
.count(); .count();
if wall_count == 1 { if wall_count == 1 {
let facing_dir = BlockDirection::horizontal() let chest_state =
.iter() crate::generation::structure::structures::StructurePiece::reorient(
.find(|d| !chunk.is_air(&chest_pos.add(&d.to_offset()))) &chest_pos,
.copied(); Block::CHEST.default_state,
|p| GenerationCache::get_block_state(chunk, p),
let chest_state = facing_dir.map_or(Block::CHEST.default_state, |dir| { );
use pumpkin_data::block_properties::ChestLikeProperties;
let mut props = ChestLikeProperties::default(&Block::CHEST);
props.facing = dir.opposite();
let state_id = props.to_state_id(&Block::CHEST);
pumpkin_data::BlockState::from_id(state_id)
});
chunk.set_block_state(&chest_pos, chest_state); chunk.set_block_state(&chest_pos, chest_state);

View File

@ -122,7 +122,11 @@ impl StructurePieceBase for BuriedTreasurePiece {
self.piece.bounding_box = BlockBox::new(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z); self.piece.bounding_box = BlockBox::new(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z);
if chunk_box.contains_pos(&pos) { if chunk_box.contains_pos(&pos) {
chunk.set_block_state(pos.x, pos.y, pos.z, Block::CHEST.default_state); let chest_state =
StructurePiece::reorient(&pos, Block::CHEST.default_state, |p| {
chunk.get_block_state(p)
});
chunk.set_block_state(pos.x, pos.y, pos.z, chest_state);
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:chest".to_string()); nbt.put_string("id", "minecraft:chest".to_string());

View File

@ -196,26 +196,23 @@ impl DesertPyramidPiece {
} }
let world_pos = self.piece.offset_pos(x, y, z); let world_pos = self.piece.offset_pos(x, y, z);
if !bb.contains_pos(&world_pos) { let mut random = RandomGenerator::Legacy(LegacyRand::from_seed(hash_block_pos(
return; world_pos.x,
world_pos.y,
world_pos.z,
) as u64));
if self.piece.add_chest(
chunk,
bb,
&mut random,
x,
y,
z,
"minecraft:chests/desert_pyramid",
) {
self.has_placed_chest[index] = true;
} }
self.piece
.add_block(chunk, Block::CHEST.default_state, x, y, z, bb);
let mut nbt = NbtCompound::new();
nbt.put_int("x", world_pos.x);
nbt.put_int("y", world_pos.y);
nbt.put_int("z", world_pos.z);
nbt.put_string("id", "minecraft:chest".to_string());
nbt.put_string("LootTable", "minecraft:chests/desert_pyramid".to_string());
let mut random =
LegacyRand::from_seed(hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64);
nbt.put_long("LootTableSeed", random.next_i64());
chunk.add_block_entity(nbt);
self.has_placed_chest[index] = true;
} }
fn add_cellar(&self, chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator) { fn add_cellar(&self, chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator) {

View File

@ -1,7 +1,6 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use pumpkin_data::Block; use pumpkin_data::{Block, BlockId, BlockState};
use pumpkin_data::BlockState;
use pumpkin_data::{Mirror, Rotation}; use pumpkin_data::{Mirror, Rotation};
use pumpkin_util::HeightMap; use pumpkin_util::HeightMap;
use pumpkin_util::{ use pumpkin_util::{
@ -464,6 +463,89 @@ impl StructurePiece {
// } // }
} }
/// Reorients a chest block state based on solid render neighbors, matching vanilla `StructurePiece.reorient`.
#[must_use]
pub fn reorient(
pos: &Vector3<i32>,
block_state: &'static BlockState,
mut get_block_state: impl FnMut(&Vector3<i32>) -> pumpkin_data::BlockStateId,
) -> &'static BlockState {
use pumpkin_data::block_properties::{ChestProperties, HorizontalFacing};
let mut solid_neighbor: Option<HorizontalFacing> = None;
let horizontal = [
HorizontalFacing::North,
HorizontalFacing::South,
HorizontalFacing::West,
HorizontalFacing::East,
];
let offset = |facing: HorizontalFacing| match facing {
HorizontalFacing::North => Vector3::new(0, 0, -1),
HorizontalFacing::South => Vector3::new(0, 0, 1),
HorizontalFacing::West => Vector3::new(-1, 0, 0),
HorizontalFacing::East => Vector3::new(1, 0, 0),
};
let opposite = |facing: HorizontalFacing| match facing {
HorizontalFacing::North => HorizontalFacing::South,
HorizontalFacing::South => HorizontalFacing::North,
HorizontalFacing::West => HorizontalFacing::East,
HorizontalFacing::East => HorizontalFacing::West,
};
let clockwise = |facing: HorizontalFacing| match facing {
HorizontalFacing::North => HorizontalFacing::East,
HorizontalFacing::East => HorizontalFacing::South,
HorizontalFacing::South => HorizontalFacing::West,
HorizontalFacing::West => HorizontalFacing::North,
};
for direction in horizontal {
let relative_pos = *pos + offset(direction);
let state = get_block_state(&relative_pos).to_state();
if state.id.to_block_id() == BlockId::CHEST {
return block_state;
}
if state.is_solid_render() {
if solid_neighbor.is_some() {
solid_neighbor = None;
break;
}
solid_neighbor = Some(direction);
}
}
let mut props = ChestProperties::from_state_id(block_state.id);
if let Some(solid_dir) = solid_neighbor {
props.facing = opposite(solid_dir);
return BlockState::from_id(props.to_state_id(&Block::CHEST));
}
let mut lock_dir = props.facing;
let mut relative_pos = *pos + offset(lock_dir);
if get_block_state(&relative_pos).to_state().is_solid_render() {
lock_dir = opposite(lock_dir);
relative_pos = *pos + offset(lock_dir);
}
if get_block_state(&relative_pos).to_state().is_solid_render() {
lock_dir = clockwise(lock_dir);
relative_pos = *pos + offset(lock_dir);
}
if get_block_state(&relative_pos).to_state().is_solid_render() {
lock_dir = opposite(lock_dir);
}
props.facing = lock_dir;
BlockState::from_id(props.to_state_id(&Block::CHEST))
}
/// Places a chest with a deferred loot table at the given local coordinates. /// Places a chest with a deferred loot table at the given local coordinates.
/// ///
/// Returns `true` if the chest was placed (i.e., the position is within the bounding box), /// Returns `true` if the chest was placed (i.e., the position is within the bounding box),
@ -478,6 +560,22 @@ impl StructurePiece {
y: i32, y: i32,
z: i32, z: i32,
loot_table: &str, loot_table: &str,
) -> bool {
self.create_chest(chunk, bb, random, x, y, z, loot_table, None)
}
/// Creates and places a chest at the given local coordinates, matching vanilla `StructurePiece.createChest`.
#[allow(clippy::too_many_arguments)]
pub fn create_chest(
&self,
chunk: &mut ProtoChunk,
bb: &BlockBox,
random: &mut RandomGenerator,
x: i32,
y: i32,
z: i32,
loot_table: &str,
block_state: Option<&'static BlockState>,
) -> bool { ) -> bool {
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
@ -486,12 +584,17 @@ impl StructurePiece {
return false; return false;
} }
chunk.set_block_state( if chunk.get_block_state(&world_pos).to_block_id() == BlockId::CHEST {
world_pos.x, return false;
world_pos.y, }
world_pos.z,
Block::CHEST.default_state, let state = block_state.unwrap_or_else(|| {
); Self::reorient(&world_pos, Block::CHEST.default_state, |p| {
chunk.get_block_state(p)
})
});
chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state);
let mut nbt = NbtCompound::new(); let mut nbt = NbtCompound::new();
nbt.put_string("id", "minecraft:chest".to_string()); nbt.put_string("id", "minecraft:chest".to_string());

View File

@ -471,9 +471,7 @@ impl StructureProcessor {
} }
Self::LavaSubmergedBlock => { Self::LavaSubmergedBlock => {
let world_state_id = placer.get_block_state(&world_pos); let world_state_id = placer.get_block_state(&world_pos);
if world_state_id.to_block_id() == BlockId::LAVA if world_state_id.to_block_id() == BlockId::LAVA && !state.is_solid_render() {
&& (!state.is_full_cube() || !state.is_solid_block())
{
return None; return None;
} }
Some(state) Some(state)

View File

@ -4,7 +4,7 @@ use crate::generation::height_limit::HeightLimitView;
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::lighting::storage::{get_block_light, get_sky_light, set_block_light, set_sky_light}; use crate::lighting::storage::{get_block_light, get_sky_light, set_block_light, set_sky_light};
use pumpkin_config::lighting::LightingEngineConfig; use pumpkin_config::lighting::LightingEngineConfig;
use pumpkin_data::{BlockDirection, BlockStateId}; use pumpkin_data::{BlockDirection, BlockState, BlockStateId};
use pumpkin_util::HeightMap; use pumpkin_util::HeightMap;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -255,6 +255,7 @@ impl<P: LightProvider> LightPropagator<P> {
self.decrease_queue.clear(); self.decrease_queue.clear();
} }
#[expect(clippy::too_many_lines)]
pub fn propagate(&mut self, cache: &mut Cache) { pub fn propagate(&mut self, cache: &mut Cache) {
let cache_x = cache.x; let cache_x = cache.x;
let cache_z = cache.z; let cache_z = cache.z;
@ -319,7 +320,12 @@ impl<P: LightProvider> LightPropagator<P> {
let op = if state_id == BlockStateId::AIR { let op = if state_id == BlockStateId::AIR {
0 0
} else { } else {
state_id.to_state().opacity let s = state_id.to_state();
if s.can_occlude() {
s.opacity.max(1)
} else {
s.opacity
}
}; };
let light = P::get_light_proto(c, section_idx, local_x, local_y, local_z); let light = P::get_light_proto(c, section_idx, local_x, local_y, local_z);
(op, light) (op, light)
@ -332,7 +338,12 @@ impl<P: LightProvider> LightPropagator<P> {
let op = if state_id == BlockStateId::AIR { let op = if state_id == BlockStateId::AIR {
0 0
} else { } else {
state_id.to_state().opacity let s = state_id.to_state();
if s.can_occlude() {
s.opacity.max(1)
} else {
s.opacity
}
}; };
(op, P::get_light(cache, neighbor_pos)) (op, P::get_light(cache, neighbor_pos))
} }
@ -390,8 +401,12 @@ impl<P: LightProvider> LightPropagator<P> {
continue; continue;
} }
let state = cache.get_block_state(&neighbor_pos.0); let state = cache.get_block_state(&neighbor_pos.0).to_state();
let opacity = state.to_state().opacity; let opacity = if state.can_occlude() {
state.opacity.max(1)
} else {
state.opacity
};
let predicted = P::propagate_level(old_val, opacity, dir); let predicted = P::propagate_level(old_val, opacity, dir);
@ -600,7 +615,12 @@ impl SkyLightPropagator {
let opacity = if state_id == BlockStateId::AIR { let opacity = if state_id == BlockStateId::AIR {
0 0
} else { } else {
state_id.to_state().opacity as i32 let s = state_id.to_state();
if s.can_occlude() {
s.opacity.max(1) as i32
} else {
s.opacity as i32
}
}; };
light = light.saturating_sub(opacity); light = light.saturating_sub(opacity);
@ -645,7 +665,12 @@ impl SkyLightPropagator {
let opacity = if state_id == BlockStateId::AIR { let opacity = if state_id == BlockStateId::AIR {
0 0
} else { } else {
state_id.to_state().opacity as i32 let s = state_id.to_state();
if s.can_occlude() {
s.opacity.max(1) as i32
} else {
s.opacity as i32
}
}; };
light = light.saturating_sub(opacity); light = light.saturating_sub(opacity);
@ -811,6 +836,53 @@ impl LightEngine {
self.sky_light.visited.clear(); self.sky_light.visited.clear();
} }
} }
/// Checks if a block state has an empty shape for light occlusion, matching vanilla `LightEngine.isEmptyShape`.
#[inline]
#[must_use]
pub const fn is_empty_shape(state: &BlockState) -> bool {
!state.can_occlude()
}
/// Checks if two block states have different light properties, matching vanilla `LightEngine.hasDifferentLightProperties`.
#[inline]
#[must_use]
pub fn has_different_light_properties(old_state: &BlockState, new_state: &BlockState) -> bool {
if std::ptr::eq(old_state, new_state) || old_state.id == new_state.id {
return false;
}
old_state.opacity != new_state.opacity
|| old_state.luminance != new_state.luminance
|| old_state.can_occlude() != new_state.can_occlude()
}
/// Returns the minimum light dampening for this block state, matching vanilla `LightEngine.getOpacity`.
#[inline]
#[must_use]
pub const fn get_opacity(state: &BlockState) -> u8 {
if state.opacity > 1 { state.opacity } else { 1 }
}
/// Calculates the light dampening between two blocks, matching vanilla `LightEngine.getLightDampeningInto`.
#[inline]
#[must_use]
pub const fn get_light_dampening_into(
from_state: &BlockState,
to_state: &BlockState,
_direction: BlockDirection,
simple_opacity: u8,
) -> u8 {
let from_empty = Self::is_empty_shape(from_state);
let to_empty = Self::is_empty_shape(to_state);
if from_empty && to_empty {
return simple_opacity;
}
if to_state.can_occlude() && to_state.is_solid_render() {
return 16;
}
simple_opacity
}
} }
impl Default for LightEngine { impl Default for LightEngine {

View File

@ -41,8 +41,8 @@ impl DynamicLightEngine {
current_pos.0.y += 1; current_pos.0.y += 1;
let state = level.get_block_state(&current_pos).to_state(); let state = level.get_block_state(&current_pos).to_state();
if state.opacity > 0 { if state.can_occlude() || state.opacity > 0 {
return false; // Hit an opaque block before reaching sky return false; // Hit an opaque or occluding block before reaching sky
} }
} }
@ -289,10 +289,18 @@ impl DynamicLightEngine {
let neighbor_light = self.get_sky_light_level(level, &neighbor_pos); let neighbor_light = self.get_sky_light_level(level, &neighbor_pos);
let neighbor_state = level.get_block_state(&neighbor_pos).to_state(); let neighbor_state = level.get_block_state(&neighbor_pos).to_state();
let opacity = neighbor_state.opacity; let opacity = if neighbor_state.can_occlude() {
neighbor_state.opacity.max(1)
} else {
neighbor_state.opacity
};
// Calculate new light level for neighbor // Calculate new light level for neighbor
let new_light = if light_level == 15 && dir == BlockDirection::Down && opacity == 0 { let new_light = if light_level == 15
&& dir == BlockDirection::Down
&& opacity == 0
&& !neighbor_state.can_occlude()
{
// Special case: Sky light at 15 propagates down as 15 through transparent blocks // Special case: Sky light at 15 propagates down as 15 through transparent blocks
15 15
} else { } else {
@ -329,10 +337,18 @@ impl DynamicLightEngine {
} }
let neighbor_state = level.get_block_state(&neighbor_pos).to_state(); let neighbor_state = level.get_block_state(&neighbor_pos).to_state();
let opacity = neighbor_state.opacity; let opacity = if neighbor_state.can_occlude() {
neighbor_state.opacity.max(1)
} else {
neighbor_state.opacity
};
// Calculate what we would have given this neighbor // Calculate what we would have given this neighbor
let expected = if removed_light == 15 && dir == BlockDirection::Down && opacity == 0 { let expected = if removed_light == 15
&& dir == BlockDirection::Down
&& opacity == 0
&& !neighbor_state.can_occlude()
{
15 15
} else { } else {
removed_light.saturating_sub(1).saturating_sub(opacity) removed_light.saturating_sub(1).saturating_sub(opacity)
@ -365,10 +381,14 @@ impl DynamicLightEngine {
let current_light = self.get_sky_light_level(level, &pos); let current_light = self.get_sky_light_level(level, &pos);
let block_state = level.get_block_state(&pos).to_state(); let block_state = level.get_block_state(&pos).to_state();
let opacity = block_state.opacity; let opacity = if block_state.can_occlude() {
block_state.opacity.max(1)
} else {
block_state.opacity
};
// Calculate expected sky light // Calculate expected sky light
let expected_light = if opacity == 15 { let expected_light = if opacity == 15 || block_state.is_solid_render() {
// Fully opaque block = no light // Fully opaque block = no light
0 0
} else { } else {
@ -387,8 +407,12 @@ impl DynamicLightEngine {
let neighbor_light = self.get_sky_light_level(level, &neighbor_pos); let neighbor_light = self.get_sky_light_level(level, &neighbor_pos);
// Calculate potential light from this neighbor // Calculate potential light from this neighbor
let potential = if neighbor_light == 15 && dir == BlockDirection::Up { let potential = if neighbor_light == 15
// Sky light at 15 from above stays 15 && dir == BlockDirection::Up
&& opacity == 0
&& !block_state.can_occlude()
{
// Sky light at 15 from above stays 15 through transparent non-occluding blocks
15 15
} else { } else {
// Normal decay // Normal decay

View File

@ -355,10 +355,7 @@ fn can_support_dripstone(support_block: &Block) -> bool {
if support_block == &Block::POINTED_DRIPSTONE { if support_block == &Block::POINTED_DRIPSTONE {
return true; return true;
} }
if support_block.default_state.is_full_cube() && support_block.default_state.is_solid_block() { support_block.default_state.is_solid_render()
return true;
}
false
} }
fn modify_dripstone_thickness_to( fn modify_dripstone_thickness_to(
world: &Arc<World>, world: &Arc<World>,

View File

@ -1,8 +1,5 @@
use pumpkin_data::block_properties::{ use pumpkin_data::block_properties::{DoubleBlockHalf, TallSeagrassLikeProperties};
DoubleBlockHalf, GrassBlockLikeProperties, TallSeagrassLikeProperties, use pumpkin_data::{Block, BlockId, BlockState, BlockStateId};
};
use pumpkin_data::tag::{self, Taggable};
use pumpkin_data::{Block, BlockDirection, BlockId, BlockState, BlockStateId};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::random::{RandomGenerator, RandomImpl, xoroshiro128::Xoroshiro}; use pumpkin_util::random::{RandomGenerator, RandomImpl, xoroshiro128::Xoroshiro};
use pumpkin_world::generation::feature::{ use pumpkin_world::generation::feature::{
@ -13,34 +10,53 @@ use pumpkin_world::tick::TickPriority;
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
use rand::RngExt; use rand::RngExt;
use crate::block::{BlockBehaviour, BlockMetadata, GetStateForNeighborUpdateArgs, OnPlaceArgs}; use super::spreading_snowy_block::{SnowyBlock, SpreadingSnowyBlock};
use crate::block::{
BlockBehaviour, BlockMetadata, GetStateForNeighborUpdateArgs, OnPlaceArgs, RandomTickArgs,
};
pub struct GrassBlock; pub struct GrassBlock;
impl BlockMetadata for GrassBlock { impl BlockMetadata for GrassBlock {
fn ids() -> Box<[BlockId]> { fn ids() -> Box<[BlockId]> {
[BlockId::GRASS_BLOCK, BlockId::MYCELIUM, BlockId::PODZOL].into() [BlockId::GRASS_BLOCK].into()
} }
} }
impl BlockBehaviour for GrassBlock { impl BlockBehaviour for GrassBlock {
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId { fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let block_above = args.world.get_block(&args.position.up()); SnowyBlock::on_place(args.block, args.world, args.position)
let mut props = GrassBlockLikeProperties::from_state_id(args.block.default_state.id); }
props.snowy = block_above.has_tag(&tag::Block::MINECRAFT_SNOW);
props.to_state_id(args.block) fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
SnowyBlock::get_state_for_neighbor_update(&args)
}
fn random_tick(&self, args: RandomTickArgs<'_>) {
let state = args.world.get_block_state(args.position);
SpreadingSnowyBlock::random_tick(
state,
args.world,
args.position,
&Block::DIRT,
Block::GRASS_BLOCK.default_state,
);
} }
fn is_valid_bonemeal_target(&self, args: crate::block::BonemealArgs<'_>) -> bool { fn is_valid_bonemeal_target(&self, args: crate::block::BonemealArgs<'_>) -> bool {
if args.block != &Block::GRASS_BLOCK {
return false;
}
let above = args.position.up(); let above = args.position.up();
args.world.is_in_height_limit(above.0.y) args.world.is_in_height_limit(above.0.y)
&& args.world.is_loaded(&above) && args.world.is_loaded(&above)
&& args.world.get_block_state(&above).is_air() && args.world.get_block_state(&above).is_air()
} }
fn is_bonemeal_success(&self, _args: crate::block::BonemealArgs<'_>) -> bool {
true
}
fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) { fn perform_bonemeal(&self, args: crate::block::BonemealArgs<'_>) {
const SPREAD_ATTEMPTS: i32 = 128; const SPREAD_ATTEMPTS: i32 = 128;
const ATTEMPTS_PER_STEP: i32 = 16; const ATTEMPTS_PER_STEP: i32 = 16;
@ -120,23 +136,6 @@ impl BlockBehaviour for GrassBlock {
} }
} }
} }
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
if args.direction == BlockDirection::Up {
let block_above = args.world.get_block(args.neighbor_position);
let mut props = GrassBlockLikeProperties::from_state_id(args.state_id);
let should_be_snowy = block_above.has_tag(&tag::Block::MINECRAFT_SNOW);
if props.snowy == should_be_snowy {
return args.state_id;
}
props.snowy = should_be_snowy;
return props.to_state_id(args.block);
}
args.state_id
}
} }
fn place_tall_grass(world: &std::sync::Arc<crate::world::World>, position: BlockPos) { fn place_tall_grass(world: &std::sync::Arc<crate::world::World>, position: BlockPos) {

View File

@ -105,6 +105,7 @@ pub mod rooted_dirt;
pub mod snow; pub mod snow;
pub mod soul_sand; pub mod soul_sand;
pub mod sponge; pub mod sponge;
pub mod spreading_snowy_block;
// Entities, hazards & misc gameplay blocks // Entities, hazards & misc gameplay blocks
pub mod anvil; pub mod anvil;

View File

@ -1,9 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, tag}; use pumpkin_data::{Block, BlockDirection, BlockState, tag};
use pumpkin_macros::pumpkin_block_from_tag; use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::lighting::LightEngine;
use pumpkin_world::world::BlockFlags; use pumpkin_world::world::BlockFlags;
use rand::RngExt; use rand::RngExt;
@ -14,19 +15,34 @@ use crate::world::World;
pub struct NyliumBlock; pub struct NyliumBlock;
impl NyliumBlock { impl NyliumBlock {
fn can_be_nylium(world: &World, pos: &BlockPos) -> bool { #[must_use]
pub(crate) const fn can_be_nylium_with_above(
state: &BlockState,
above_state: &BlockState,
) -> bool {
let dampening = LightEngine::get_light_dampening_into(
state,
above_state,
BlockDirection::Up,
above_state.opacity,
);
dampening < 15
}
fn can_be_nylium(state: &BlockState, world: &World, pos: &BlockPos) -> bool {
let above_pos = pos.up(); let above_pos = pos.up();
if !world.is_loaded(&above_pos) { if !world.is_loaded(&above_pos) {
return true; return true;
} }
let above_state = world.get_block_state(&above_pos); let above_state = world.get_block_state(&above_pos);
above_state.opacity < 15 Self::can_be_nylium_with_above(state, above_state)
} }
} }
impl BlockBehaviour for NyliumBlock { impl BlockBehaviour for NyliumBlock {
fn random_tick(&self, args: RandomTickArgs<'_>) { fn random_tick(&self, args: RandomTickArgs<'_>) {
if !Self::can_be_nylium(args.world, args.position) { let state = args.world.get_block_state(args.position);
if !Self::can_be_nylium(state, args.world, args.position) {
args.world.set_block_state( args.world.set_block_state(
args.position, args.position,
Block::NETHERRACK.default_state.id, Block::NETHERRACK.default_state.id,

View File

@ -33,7 +33,7 @@ fn mushroom_tree_height(rng: &mut impl rand::Rng) -> i32 {
impl MushroomPlantBlock { impl MushroomPlantBlock {
#[must_use] #[must_use]
pub const fn may_place_on(state: &BlockState) -> bool { pub const fn may_place_on(state: &BlockState) -> bool {
state.is_solid() && (state.is_full_cube() || state.is_solid_block()) state.is_solid_render()
} }
pub fn can_survive( pub fn can_survive(

View File

@ -4,6 +4,7 @@ use crate::block::entities::comparator::ComparatorBlockEntity;
use pumpkin_data::{ use pumpkin_data::{
Block, BlockDirection, BlockState, BlockStateId, HorizontalFacingExt, Block, BlockDirection, BlockState, BlockStateId, HorizontalFacingExt,
block_properties::{ComparatorLikeProperties, HorizontalFacing, ModeComparator}, block_properties::{ComparatorLikeProperties, HorizontalFacing, ModeComparator},
sound::{Sound, SoundCategory},
}; };
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos}; use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos};
@ -31,9 +32,44 @@ impl BlockBehaviour for ComparatorBlock {
} }
fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult { fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if !args.player.may_build() {
return BlockActionResult::Pass;
}
let state = args.world.get_block_state(args.position); let state = args.world.get_block_state(args.position);
let props = ComparatorLikeProperties::from_state_id(state.id); let mut props = ComparatorLikeProperties::from_state_id(state.id);
self.on_use(props, args.world, *args.position, args.block);
props.mode = match props.mode {
ModeComparator::Compare => ModeComparator::Subtract,
ModeComparator::Subtract => ModeComparator::Compare,
};
let pitch = match props.mode {
ModeComparator::Subtract => 0.55,
ModeComparator::Compare => 0.5,
};
args.world.play_sound_raw_expect(
args.player,
Sound::BlockComparatorClick as u16,
SoundCategory::Blocks,
&args.position.to_centered_f64(),
0.3,
pitch,
);
let state_id = props.to_state_id(args.block);
args.world
.set_block_state(args.position, state_id, BlockFlags::NOTIFY_LISTENERS);
if args.world.get_block(args.position) == args.block {
self.update(
args.world,
*args.position,
BlockState::from_id(state_id),
args.block,
);
}
BlockActionResult::Success BlockActionResult::Success
} }
@ -98,7 +134,7 @@ impl BlockBehaviour for ComparatorBlock {
fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) { fn on_scheduled_tick(&self, args: OnScheduledTickArgs<'_>) {
let (block, state) = args.world.get_block_and_state(args.position); let (block, state) = args.world.get_block_and_state(args.position);
Self.update(args.world, *args.position, state, block); self.update(args.world, *args.position, state, block);
} }
fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) { fn on_state_replaced(&self, args: OnStateReplacedArgs<'_>) {
@ -138,7 +174,7 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
let j = RedstoneGateBlock::get_output_level(self, world, pos); let j = RedstoneGateBlock::get_output_level(self, world, pos);
let props = ComparatorLikeProperties::from_state_id(state.id); let props = ComparatorLikeProperties::from_state_id(state.id);
if i != j || props.powered != RedstoneGateBlock::has_power(self, world, pos, state, block) { if i != j || props.powered != self.has_power(world, pos, state, block) {
let priority = let priority =
if RedstoneGateBlock::is_target_not_aligned(self, world, pos, state, block) { if RedstoneGateBlock::is_target_not_aligned(self, world, pos, state, block) {
TickPriority::High TickPriority::High
@ -156,7 +192,7 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
} }
fn has_power(&self, world: &World, pos: BlockPos, state: &BlockState, block: &Block) -> bool { fn has_power(&self, world: &World, pos: BlockPos, state: &BlockState, block: &Block) -> bool {
let i = RedstoneGateBlock::get_power(self, world, pos, state, block); let i = self.get_power(world, pos, state, block);
if i == 0 { if i == 0 {
return false; return false;
} }
@ -172,50 +208,64 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
} }
fn get_power(&self, world: &World, pos: BlockPos, state: &BlockState, block: &Block) -> u8 { fn get_power(&self, world: &World, pos: BlockPos, state: &BlockState, block: &Block) -> u8 {
let redstone_level = abstract_redstone_gate::get_power::<ComparatorLikeProperties>( let mut result_signal = abstract_redstone_gate::get_power::<ComparatorLikeProperties>(
world, pos, state.id, block, world, pos, state.id, block,
); );
let props = ComparatorLikeProperties::from_state_id(state.id); let props = ComparatorLikeProperties::from_state_id(state.id);
let facing = props.facing; let facing = props.facing;
let source_pos = pos.offset(facing.to_offset()); let mut target_pos = pos.offset(facing.to_offset());
let (source_block, source_state) = world.get_block_and_state(&source_pos); let (target_block, target_state) = world.get_block_and_state(&target_pos);
if let Some(pumpkin_block) = world.block_registry.get_pumpkin_block(source_block.id) if target_state.has_analog_output_signal() {
&& let Some(level) = pumpkin_block.get_comparator_output(GetComparatorOutputArgs { result_signal = world
world,
block: source_block,
state: source_state,
position: &source_pos,
})
{
return level;
}
if redstone_level < 15 && source_state.is_solid_block() {
let deeper_source_pos = source_pos.offset(facing.to_offset());
let (deeper_block, deeper_state) = world.get_block_and_state(&deeper_source_pos);
let itemframe_level =
Self::get_attached_itemframe_level(world, facing, deeper_source_pos);
let block_level = world
.block_registry .block_registry
.get_pumpkin_block(deeper_block.id) .get_pumpkin_block(target_block.id)
.and_then(|pumpkin_block| { .and_then(|pumpkin_block| {
pumpkin_block.get_comparator_output(GetComparatorOutputArgs { pumpkin_block.get_comparator_output(GetComparatorOutputArgs {
world, world,
block: deeper_block, block: target_block,
state: deeper_state, state: target_state,
position: &deeper_source_pos, position: &target_pos,
}) })
}); })
.unwrap_or(0);
} else if result_signal < 15 && target_state.is_solid_block() {
target_pos = target_pos.offset(facing.to_offset());
let (target_block, target_state) = world.get_block_and_state(&target_pos);
if let Some(level) = itemframe_level.max(block_level) { let item_frame_signal =
return level; Self::get_attached_itemframe_level(world, facing, target_pos).map(i32::from);
let block_signal = target_state.has_analog_output_signal().then(|| {
let signal = world
.block_registry
.get_pumpkin_block(target_block.id)
.and_then(|pumpkin_block| {
pumpkin_block.get_comparator_output(GetComparatorOutputArgs {
world,
block: target_block,
state: target_state,
position: &target_pos,
})
})
.unwrap_or(0);
i32::from(signal)
});
let item_frame_or_block_signal = match (item_frame_signal, block_signal) {
(Some(frame), Some(block)) => Some(frame.max(block)),
(Some(frame), None) => Some(frame),
(None, Some(block)) => Some(block),
(None, None) => None,
};
if let Some(signal) = item_frame_or_block_signal {
result_signal = signal as u8;
} }
} }
redstone_level
result_signal
} }
fn get_update_delay_internal(&self, _state_id: BlockStateId, _block: &Block) -> u8 { fn get_update_delay_internal(&self, _state_id: BlockStateId, _block: &Block) -> u8 {
@ -224,28 +274,6 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
} }
impl ComparatorBlock { impl ComparatorBlock {
fn on_use(
&self,
mut props: ComparatorLikeProperties,
world: &Arc<World>,
block_pos: BlockPos,
block: &Block,
) {
// Vanilla Parity TODO:
// playSound(player, pos, SoundEvents.COMPARATOR_CLICK, SoundSource.BLOCKS, 0.3F, pitch);
// Pitch is 0.55F if SUBTRACT, 0.5F if COMPARE.
props.mode = match props.mode {
ModeComparator::Compare => ModeComparator::Subtract,
ModeComparator::Subtract => ModeComparator::Compare,
};
let state_id = props.to_state_id(block);
world.set_block_state(&block_pos, state_id, BlockFlags::NOTIFY_ALL);
self.update(world, block_pos, BlockState::from_id(state_id), block);
}
fn calculate_output_signal( fn calculate_output_signal(
&self, &self,
world: &World, world: &World,

View File

@ -0,0 +1,213 @@
use std::sync::Arc;
use pumpkin_data::block_properties::{
GrassBlockLikeProperties, SnowLikeProperties, WaterLikeProperties,
};
use pumpkin_data::tag::{self, Taggable};
use pumpkin_data::{Block, BlockDirection, BlockId, BlockState, BlockStateId};
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::lighting::LightEngine;
use pumpkin_world::world::BlockFlags;
use rand::RngExt;
use crate::block::{
BlockBehaviour, BlockMetadata, GetStateForNeighborUpdateArgs, OnPlaceArgs, RandomTickArgs,
};
use crate::world::World;
/// Base logic for snowy blocks, matching vanilla `net.minecraft.world.level.block.SnowyBlock`.
pub struct SnowyBlock;
impl SnowyBlock {
#[must_use]
pub fn is_snowy_setting(above_state: &BlockState) -> bool {
above_state
.id
.to_block()
.has_tag(&tag::Block::MINECRAFT_SNOW)
}
#[must_use]
pub fn on_place(block: &Block, world: &World, position: &BlockPos) -> BlockStateId {
let block_above = world.get_block(&position.up());
let mut props = GrassBlockLikeProperties::from_state_id(block.default_state.id);
props.snowy = block_above.has_tag(&tag::Block::MINECRAFT_SNOW);
props.to_state_id(block)
}
#[must_use]
pub fn get_state_for_neighbor_update(args: &GetStateForNeighborUpdateArgs<'_>) -> BlockStateId {
if args.direction == BlockDirection::Up {
let block_above = args.world.get_block(args.neighbor_position);
let mut props = GrassBlockLikeProperties::from_state_id(args.state_id);
let should_be_snowy = block_above.has_tag(&tag::Block::MINECRAFT_SNOW);
if props.snowy == should_be_snowy {
return args.state_id;
}
props.snowy = should_be_snowy;
return props.to_state_id(args.block);
}
args.state_id
}
}
/// Abstract base logic for spreading snowy blocks (e.g. Grass Block, Mycelium),
/// matching vanilla `net.minecraft.world.level.block.SpreadingSnowyBlock`.
pub struct SpreadingSnowyBlock;
impl SpreadingSnowyBlock {
#[must_use]
pub fn is_full_fluid(world: &World, pos: &BlockPos) -> bool {
let state = world.get_block_state(pos);
let block = state.id.to_block();
if block == &Block::WATER || block == &Block::LAVA {
return WaterLikeProperties::from_state_id(state.id).level == 0;
}
state.id.is_waterlogged()
}
#[must_use]
pub fn is_water_fluid(world: &World, pos: &BlockPos) -> bool {
let state = world.get_block_state(pos);
let block = state.id.to_block();
block == &Block::WATER || state.id.is_waterlogged()
}
#[must_use]
pub fn can_stay_alive_with_above(
state: &BlockState,
above_state: &BlockState,
is_full_fluid: bool,
) -> bool {
if above_state.id.to_block() == &Block::SNOW {
let props = SnowLikeProperties::from_state_id(above_state.id);
if props.layers == 1 {
return true;
}
}
if is_full_fluid {
return false;
}
let light_dampening_top_face = LightEngine::get_light_dampening_into(
state,
above_state,
BlockDirection::Up,
above_state.opacity,
);
light_dampening_top_face < 15
}
#[must_use]
pub fn can_stay_alive(state: &BlockState, world: &World, pos: &BlockPos) -> bool {
let above = pos.up();
if !world.is_loaded(&above) {
return true;
}
let above_state = world.get_block_state(&above);
let is_full_fluid = Self::is_full_fluid(world, &above);
Self::can_stay_alive_with_above(state, above_state, is_full_fluid)
}
#[must_use]
pub fn can_propagate(state: &BlockState, world: &World, pos: &BlockPos) -> bool {
let above = pos.up();
if !world.is_loaded(&above) {
return false;
}
Self::can_stay_alive(state, world, pos) && !Self::is_water_fluid(world, &above)
}
pub fn random_tick(
state: &BlockState,
world: &Arc<World>,
pos: &BlockPos,
base_block: &'static Block,
default_block_state: &'static BlockState,
) {
if !Self::can_stay_alive(state, world, pos) {
world.set_block_state(pos, base_block.default_state.id, BlockFlags::NOTIFY_ALL);
} else if world.get_max_local_raw_brightness(&pos.up()) >= 9 {
let mut rng = rand::rng();
for _ in 0..4 {
let dx = rng.random_range(0..3) - 1;
let dy = rng.random_range(0..5) - 3;
let dz = rng.random_range(0..3) - 1;
let test_pos = pos.add(dx, dy, dz);
if !world.is_loaded(&test_pos) {
continue;
}
if world.get_block(&test_pos) == base_block
&& Self::can_propagate(default_block_state, world, &test_pos)
{
let above_test = test_pos.up();
let is_snowy = if world.is_loaded(&above_test) {
SnowyBlock::is_snowy_setting(world.get_block_state(&above_test))
} else {
false
};
let mut props = GrassBlockLikeProperties::from_state_id(default_block_state.id);
props.snowy = is_snowy;
let new_state_id = props.to_state_id(default_block_state.id.to_block());
world.set_block_state(&test_pos, new_state_id, BlockFlags::NOTIFY_ALL);
}
}
}
}
}
/// Podzol block, matching vanilla `net.minecraft.world.level.block.SnowyBlock` with `BlockItemIds.PODZOL`.
pub struct PodzolBlock;
impl BlockMetadata for PodzolBlock {
fn ids() -> Box<[BlockId]> {
[BlockId::PODZOL].into()
}
}
impl BlockBehaviour for PodzolBlock {
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
SnowyBlock::on_place(args.block, args.world, args.position)
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
SnowyBlock::get_state_for_neighbor_update(&args)
}
}
/// Mycelium block, matching vanilla `net.minecraft.world.level.block.MyceliumBlock`.
pub struct MyceliumBlock;
impl BlockMetadata for MyceliumBlock {
fn ids() -> Box<[BlockId]> {
[BlockId::MYCELIUM].into()
}
}
impl BlockBehaviour for MyceliumBlock {
fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
SnowyBlock::on_place(args.block, args.world, args.position)
}
fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
SnowyBlock::get_state_for_neighbor_update(&args)
}
fn random_tick(&self, args: RandomTickArgs<'_>) {
let state = args.world.get_block_state(args.position);
SpreadingSnowyBlock::random_tick(
state,
args.world,
args.position,
&Block::DIRT,
Block::MYCELIUM.default_state,
);
}
}

View File

@ -153,6 +153,7 @@ use crate::block::blocks::snow::LayeredSnowBlock;
use crate::block::blocks::soul_sand::SoulSandBlock; use crate::block::blocks::soul_sand::SoulSandBlock;
use crate::block::blocks::spawner::SpawnerBlock; use crate::block::blocks::spawner::SpawnerBlock;
use crate::block::blocks::sponge::{SpongeBlock, WetSpongeBlock}; use crate::block::blocks::sponge::{SpongeBlock, WetSpongeBlock};
use crate::block::blocks::spreading_snowy_block::{MyceliumBlock, PodzolBlock};
use crate::block::blocks::stairs::StairBlock; use crate::block::blocks::stairs::StairBlock;
use crate::block::blocks::structure_block::StructureBlock; use crate::block::blocks::structure_block::StructureBlock;
use crate::block::blocks::structure_void::StructureVoidBlock; use crate::block::blocks::structure_void::StructureVoidBlock;
@ -401,6 +402,8 @@ pub fn default_registry() -> Arc<BlockRegistry> {
manager.register(AmethystBlock); manager.register(AmethystBlock);
manager.register(BuddingAmethystBlock); manager.register(BuddingAmethystBlock);
manager.register(GrassBlock); manager.register(GrassBlock);
manager.register(MyceliumBlock);
manager.register(PodzolBlock);
manager.register(RootedDirtBlock); manager.register(RootedDirtBlock);
manager.register(NyliumBlock); manager.register(NyliumBlock);
manager.register(BubbleColumnBlock); manager.register(BubbleColumnBlock);

View File

@ -5357,8 +5357,10 @@ impl World {
} }
if flags.contains(BlockFlags::NOTIFY_NEIGHBORS) { if flags.contains(BlockFlags::NOTIFY_NEIGHBORS) {
self.update_neighbors(position, None); self.update_neighbors_at(position, old_block, None);
// TODO: updateNeighbourForOutputSignal if blockState.hasAnalogOutputSignal() if block_state_id.has_analog_output_signal() {
self.update_neighbour_for_output_signal(position, new_block);
}
} }
if !flags.contains(BlockFlags::MOVED) { if !flags.contains(BlockFlags::MOVED) {
@ -5402,9 +5404,15 @@ impl World {
} }
} }
self.level let old_state = replaced_block_state_id.to_state();
.light_engine let new_state = block_state_id.to_state();
.update_lighting_at(&self.level, *position); if pumpkin_world::lighting::LightEngine::has_different_light_properties(
old_state, new_state,
) {
self.level
.light_engine
.update_lighting_at(&self.level, *position);
}
replaced_block_state_id replaced_block_state_id
} }
@ -5896,13 +5904,13 @@ impl World {
(Block::from_state_id(id), id) (Block::from_state_id(id), id)
} }
/// Updates neighboring blocks of a block /// Updates neighboring blocks of a block with a specified source block
pub fn update_neighbors( pub fn update_neighbors_at(
self: &Arc<Self>, self: &Arc<Self>,
block_pos: &BlockPos, block_pos: &BlockPos,
source_block: &Block,
except: Option<BlockDirection>, except: Option<BlockDirection>,
) { ) {
let source_block = self.get_block(block_pos);
for direction in BlockDirection::update_order() { for direction in BlockDirection::update_order() {
if except.is_some_and(|d| d == direction) { if except.is_some_and(|d| d == direction) {
continue; continue;
@ -5948,6 +5956,16 @@ impl World {
} }
} }
/// Updates neighboring blocks of a block
pub fn update_neighbors(
self: &Arc<Self>,
block_pos: &BlockPos,
except: Option<BlockDirection>,
) {
let source_block = self.get_block(block_pos);
self.update_neighbors_at(block_pos, source_block, except);
}
pub fn update_neighbor(self: &Arc<Self>, neighbor_block_pos: &BlockPos, source_block: &Block) { pub fn update_neighbor(self: &Arc<Self>, neighbor_block_pos: &BlockPos, source_block: &Block) {
let neighbor_block = self.get_block(neighbor_block_pos); let neighbor_block = self.get_block(neighbor_block_pos);
@ -5975,6 +5993,30 @@ impl World {
} }
} }
pub fn update_neighbour_for_output_signal(
self: &Arc<Self>,
pos: &BlockPos,
changed_block: &Block,
) {
for direction in BlockDirection::horizontal() {
let mut relative_pos = pos.offset(direction.to_offset());
if self.is_loaded(&relative_pos) {
let state = self.get_block_state(&relative_pos);
if state.id.to_block() == &Block::COMPARATOR {
self.update_neighbor(&relative_pos, changed_block);
} else if state.is_solid_block() {
relative_pos = relative_pos.offset(direction.to_offset());
if self.is_loaded(&relative_pos) {
let second_state = self.get_block_state(&relative_pos);
if second_state.id.to_block() == &Block::COMPARATOR {
self.update_neighbor(&relative_pos, changed_block);
}
}
}
}
}
}
pub fn update_from_neighbor_shapes( pub fn update_from_neighbor_shapes(
self: &Arc<Self>, self: &Arc<Self>,
state_id: BlockStateId, state_id: BlockStateId,

View File

@ -635,6 +635,10 @@ impl BlockState {
/// Bit flag indicating this state receives random tick events. /// Bit flag indicating this state receives random tick events.
const HAS_RANDOM_TICKS: u16 = 1 << 9; const HAS_RANDOM_TICKS: u16 = 1 << 9;
const IS_SOLID_RENDER: u16 = 1 << 10;
const CAN_OCCLUDE: u16 = 1 << 11;
const HAS_ANALOG_OUTPUT_SIGNAL: u16 = 1 << 12;
/// Returns `true` if this state receives random tick events. /// Returns `true` if this state receives random tick events.
const fn has_random_ticks(&self) -> bool { const fn has_random_ticks(&self) -> bool {
self.state_flags & Self::HAS_RANDOM_TICKS != 0 self.state_flags & Self::HAS_RANDOM_TICKS != 0
@ -649,6 +653,18 @@ impl BlockState {
self.state_flags & Self::IS_LIQUID != 0 self.state_flags & Self::IS_LIQUID != 0
} }
pub const fn is_solid_render(&self) -> bool {
self.state_flags & Self::IS_SOLID_RENDER != 0
}
pub const fn can_occlude(&self) -> bool {
self.state_flags & Self::CAN_OCCLUDE != 0
}
pub const fn has_analog_output_signal(&self) -> bool {
self.state_flags & Self::HAS_ANALOG_OUTPUT_SIGNAL != 0
}
/// Emits the `BlockState { … }` struct literal token stream for code generation. /// Emits the `BlockState { … }` struct literal token stream for code generation.
fn to_tokens(&self) -> TokenStream { fn to_tokens(&self) -> TokenStream {
let mut tokens = TokenStream::new(); let mut tokens = TokenStream::new();