fix: more fixes
This commit is contained in:
parent
21e83e9ed9
commit
287404dde5
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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());
|
||||||
|
|||||||
@ -196,27 +196,24 @@ 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));
|
||||||
|
|
||||||
self.piece
|
if self.piece.add_chest(
|
||||||
.add_block(chunk, Block::CHEST.default_state, x, y, z, bb);
|
chunk,
|
||||||
|
bb,
|
||||||
let mut nbt = NbtCompound::new();
|
&mut random,
|
||||||
nbt.put_int("x", world_pos.x);
|
x,
|
||||||
nbt.put_int("y", world_pos.y);
|
y,
|
||||||
nbt.put_int("z", world_pos.z);
|
z,
|
||||||
nbt.put_string("id", "minecraft:chest".to_string());
|
"minecraft:chests/desert_pyramid",
|
||||||
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;
|
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) {
|
||||||
let mut potential_positions = Vec::new();
|
let mut potential_positions = Vec::new();
|
||||||
|
|||||||
@ -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());
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -41,8 +41,8 @@ impl DynamicLightEngine {
|
|||||||
current_pos.0.y += 1;
|
current_pos.0.y += 1;
|
||||||
|
|
||||||
let state = level.get_block_state(¤t_pos).to_state();
|
let state = level.get_block_state(¤t_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
|
||||||
|
|||||||
@ -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>,
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
let item_frame_signal =
|
||||||
|
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)
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(level) = itemframe_level.max(block_level) {
|
let item_frame_or_block_signal = match (item_frame_signal, block_signal) {
|
||||||
return level;
|
(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,
|
||||||
|
|||||||
213
crates/pumpkin/src/block/blocks/spreading_snowy_block.rs
Normal file
213
crates/pumpkin/src/block/blocks/spreading_snowy_block.rs
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
|||||||
@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let old_state = replaced_block_state_id.to_state();
|
||||||
|
let new_state = block_state_id.to_state();
|
||||||
|
if pumpkin_world::lighting::LightEngine::has_different_light_properties(
|
||||||
|
old_state, new_state,
|
||||||
|
) {
|
||||||
self.level
|
self.level
|
||||||
.light_engine
|
.light_engine
|
||||||
.update_lighting_at(&self.level, *position);
|
.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,
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user