chore: made tnt explosion bit faster
This commit is contained in:
parent
39596c1b9f
commit
322b7af6ae
@ -1367,7 +1367,22 @@ pub fn build() -> TokenStream {
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn to_axis(&self) -> HorizontalAxis {
|
||||
match self {
|
||||
Self::North | Self::South => HorizontalAxis::Z,
|
||||
Self::West | Self::East => HorizontalAxis::X,
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn to_facing(&self) -> HorizontalFacing {
|
||||
match self {
|
||||
Self::North => HorizontalFacing::North,
|
||||
Self::South => HorizontalFacing::South,
|
||||
Self::West => HorizontalFacing::West,
|
||||
Self::East => HorizontalFacing::East,
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn opposite(&self) -> Self {
|
||||
match self {
|
||||
|
||||
@ -84,7 +84,7 @@ impl BlockDirection {
|
||||
Self::all()[random.next_bounded_i32(Self::all().len() as i32 - 1) as usize]
|
||||
}
|
||||
|
||||
pub fn random_horizontal(random: &mut RandomGenerator) -> Self {
|
||||
pub fn random_horizontal(random: &mut RandomGenerator) -> HorizontalFacing {
|
||||
Self::horizontal()[random.next_bounded_i32(Self::horizontal().len() as i32 - 1) as usize]
|
||||
}
|
||||
|
||||
@ -159,8 +159,13 @@ impl BlockDirection {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn horizontal() -> [Self; 4] {
|
||||
[Self::North, Self::South, Self::West, Self::East]
|
||||
pub const fn horizontal() -> [HorizontalFacing; 4] {
|
||||
[
|
||||
HorizontalFacing::North,
|
||||
HorizontalFacing::South,
|
||||
HorizontalFacing::West,
|
||||
HorizontalFacing::East,
|
||||
]
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@ -261,6 +266,7 @@ impl BlockDirection {
|
||||
|
||||
pub trait FacingExt {
|
||||
fn to_block_direction(&self) -> BlockDirection;
|
||||
fn to_horizontal_facing(&self) -> Option<HorizontalFacing>;
|
||||
}
|
||||
|
||||
impl FacingExt for Facing {
|
||||
@ -274,6 +280,15 @@ impl FacingExt for Facing {
|
||||
Self::Down => BlockDirection::Down,
|
||||
}
|
||||
}
|
||||
fn to_horizontal_facing(&self) -> Option<HorizontalFacing> {
|
||||
match self {
|
||||
Self::North => Some(HorizontalFacing::North),
|
||||
Self::South => Some(HorizontalFacing::South),
|
||||
Self::West => Some(HorizontalFacing::West),
|
||||
Self::East => Some(HorizontalFacing::East),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HorizontalFacingExt {
|
||||
|
||||
@ -107,7 +107,7 @@ impl BlockState {
|
||||
self.state_flags & HAS_RANDOM_TICKS != 0
|
||||
}
|
||||
|
||||
///`isSideSolidFullSquare()` in Java!
|
||||
///`isFaceSturdy()` in Java!
|
||||
#[must_use]
|
||||
pub const fn is_side_solid(&self, side: BlockDirection) -> bool {
|
||||
match side {
|
||||
|
||||
@ -560796,6 +560796,22 @@ impl HorizontalFacing {
|
||||
.into()
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn to_axis(&self) -> HorizontalAxis {
|
||||
match self {
|
||||
Self::North | Self::South => HorizontalAxis::Z,
|
||||
Self::West | Self::East => HorizontalAxis::X,
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn to_facing(&self) -> HorizontalFacing {
|
||||
match self {
|
||||
Self::North => HorizontalFacing::North,
|
||||
Self::South => HorizontalFacing::South,
|
||||
Self::West => HorizontalFacing::West,
|
||||
Self::East => HorizontalFacing::East,
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn opposite(&self) -> Self {
|
||||
match self {
|
||||
Self::North => Self::South,
|
||||
|
||||
@ -2,6 +2,7 @@ use pumpkin_data::{
|
||||
Block, BlockDirection,
|
||||
block_properties::{
|
||||
BlockProperties, BrownMushroomBlockLikeProperties, ChorusFlowerLikeProperties,
|
||||
HorizontalFacing,
|
||||
},
|
||||
tag,
|
||||
};
|
||||
@ -51,7 +52,7 @@ impl ChorusPlantFeature {
|
||||
fn all_neighbors_empty<T: GenerationCache>(
|
||||
chunk: &T,
|
||||
pos: &BlockPos,
|
||||
ignore: Option<BlockDirection>,
|
||||
ignore: Option<HorizontalFacing>,
|
||||
) -> bool {
|
||||
for dir in BlockDirection::horizontal() {
|
||||
if Some(dir) == ignore {
|
||||
@ -139,12 +140,9 @@ fn grow_tree_recursive<T: GenerationCache>(
|
||||
stems += 1;
|
||||
}
|
||||
|
||||
// The 4 horizontal directions
|
||||
const HORIZ: [BlockDirection; 4] = BlockDirection::horizontal();
|
||||
|
||||
for _ in 0..stems {
|
||||
// Pick a random horizontal direction
|
||||
let dir = HORIZ[random.next_bounded_i32(4) as usize];
|
||||
let dir = BlockDirection::horizontal()[random.next_bounded_i32(4) as usize];
|
||||
let target = top.offset(dir.to_offset());
|
||||
let target_below = target.down();
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ impl CoralClawFeature {
|
||||
k = random.next_bounded_i32(3) + 2;
|
||||
} else {
|
||||
pos = pos.up();
|
||||
let _directions = [direction2, BlockDirection::Up];
|
||||
//let _directions = [direction2, BlockDirection::Up];
|
||||
direction3 = direction2; // TODO: make this random
|
||||
k = random.next_bounded_i32(3) + 5;
|
||||
}
|
||||
|
||||
@ -68,13 +68,12 @@ impl CoralFeature {
|
||||
.properties(wall_coral.default_state.id)
|
||||
.unwrap()
|
||||
.to_props();
|
||||
let facing = dir.to_facing();
|
||||
// Set the right Axis
|
||||
let props: Vec<(&str, &str)> = original_props
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
if *key == "facing" {
|
||||
(*key, facing.to_value())
|
||||
(*key, dir.to_value())
|
||||
} else {
|
||||
(*key, *value)
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@ impl DungeonFeature {
|
||||
BlockProperties, ChestLikeProperties,
|
||||
};
|
||||
let mut props = ChestLikeProperties::default(&Block::CHEST);
|
||||
props.facing = dir.opposite().to_cardinal_direction();
|
||||
props.facing = dir.opposite();
|
||||
let state_id = props.to_state_id(&Block::CHEST);
|
||||
pumpkin_data::BlockState::from_id(state_id)
|
||||
} else {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use pumpkin_data::{BlockDirection, BlockState, fluid::Fluid};
|
||||
use pumpkin_data::{BlockDirection, BlockState, block_properties::HorizontalFacing, fluid::Fluid};
|
||||
use pumpkin_util::{
|
||||
math::{int_provider::IntProvider, position::BlockPos},
|
||||
random::{RandomGenerator, RandomImpl},
|
||||
@ -78,7 +78,7 @@ impl MangroveRootPlacer {
|
||||
chunk: &mut T,
|
||||
random: &mut RandomGenerator,
|
||||
pos: BlockPos,
|
||||
direction: BlockDirection,
|
||||
direction: HorizontalFacing,
|
||||
origin: BlockPos,
|
||||
offshoots: &mut Vec<BlockPos>,
|
||||
root_length: i32,
|
||||
@ -109,7 +109,7 @@ impl MangroveRootPlacer {
|
||||
fn get_offshoot_positions(
|
||||
&self,
|
||||
pos: BlockPos,
|
||||
direction: BlockDirection,
|
||||
direction: HorizontalFacing,
|
||||
random: &mut RandomGenerator,
|
||||
origin: BlockPos,
|
||||
) -> Vec<BlockPos> {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use pumpkin_data::BlockState;
|
||||
use pumpkin_data::block_properties::HorizontalFacing;
|
||||
use pumpkin_util::math::int_provider::IntProvider;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::random::{RandomGenerator, RandomImpl};
|
||||
@ -85,7 +86,7 @@ impl UpwardsBranchingTrunkPlacer {
|
||||
nodes: &mut Vec<TreeNode>,
|
||||
logs: &mut Vec<BlockPos>,
|
||||
current_pos: BlockPos,
|
||||
branch_dir: BlockDirection,
|
||||
branch_dir: HorizontalFacing,
|
||||
branch_pos: i32,
|
||||
mut branch_steps: i32,
|
||||
) {
|
||||
|
||||
@ -9,7 +9,8 @@ use crate::{
|
||||
use pumpkin_data::{
|
||||
Block, BlockDirection, FacingExt, HorizontalFacingExt,
|
||||
block_properties::{
|
||||
BlockProperties, Facing, LadderLikeProperties, MangroveRootsLikeProperties,
|
||||
BlockProperties, Facing, HorizontalFacing, LadderLikeProperties,
|
||||
MangroveRootsLikeProperties,
|
||||
},
|
||||
tag::{self, Taggable},
|
||||
};
|
||||
@ -18,7 +19,9 @@ use pumpkin_world::{
|
||||
BlockStateId,
|
||||
world::{BlockAccessor, BlockFlags},
|
||||
};
|
||||
|
||||
pub struct CoralFanBlock;
|
||||
|
||||
impl BlockMetadata for CoralFanBlock {
|
||||
fn ids() -> Box<[u16]> {
|
||||
let alive_wall_fans = tag::Block::MINECRAFT_WALL_CORALS.1;
|
||||
@ -81,18 +84,18 @@ impl BlockBehaviour for CoralFanBlock {
|
||||
for dir in directions {
|
||||
if dir != Facing::Up
|
||||
&& dir != Facing::Down
|
||||
&& can_place_at(args.world, args.position, dir.to_block_direction())
|
||||
&& can_place_at(
|
||||
args.world,
|
||||
args.position,
|
||||
dir.to_horizontal_facing().unwrap(),
|
||||
)
|
||||
{
|
||||
let Some(wall_block) = get_corresponding_wall_fan_type(args.block) else {
|
||||
return 0;
|
||||
};
|
||||
let mut coral_wall_fan_props = CoralWallFanLikeProperties::default(&wall_block);
|
||||
coral_wall_fan_props.waterlogged = args.replacing.water_source();
|
||||
coral_wall_fan_props.facing = dir
|
||||
.opposite()
|
||||
.to_block_direction()
|
||||
.to_horizontal_facing()
|
||||
.unwrap();
|
||||
coral_wall_fan_props.facing = dir.opposite().to_horizontal_facing().unwrap();
|
||||
return coral_wall_fan_props.to_state_id(&wall_block);
|
||||
}
|
||||
}
|
||||
@ -104,6 +107,7 @@ impl BlockBehaviour for CoralFanBlock {
|
||||
0
|
||||
})
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !scan_for_water(args.world, args.position).await {
|
||||
@ -111,24 +115,32 @@ impl BlockBehaviour for CoralFanBlock {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if !scan_for_water(args.world, args.position).await && !is_dead_coral(args.block) {
|
||||
let current_state = args.world.get_block_state(args.position);
|
||||
|
||||
// VANILLA FIX: Explicitly set waterlogged to false when dying
|
||||
let dead_block_state_id = if is_wall_fan(args.block) {
|
||||
let props =
|
||||
let mut props =
|
||||
CoralWallFanLikeProperties::from_state_id(current_state.id, args.block);
|
||||
props.waterlogged = false;
|
||||
props.to_state_id(&get_dead_type(args.block))
|
||||
} else {
|
||||
let props = CoralFanLikeProperties::from_state_id(current_state.id, args.block);
|
||||
let mut props =
|
||||
CoralFanLikeProperties::from_state_id(current_state.id, args.block);
|
||||
props.waterlogged = false;
|
||||
props.to_state_id(&get_dead_type(args.block))
|
||||
};
|
||||
|
||||
args.world
|
||||
.set_block_state(args.position, dead_block_state_id, BlockFlags::empty())
|
||||
.await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn can_place_at<'a>(&'a self, args: CanPlaceAtArgs<'a>) -> bool {
|
||||
let support_block = args.block_accessor.get_block_state(&args.position.down());
|
||||
if support_block.is_center_solid(BlockDirection::Up) && !is_wall_fan(args.block) {
|
||||
@ -150,11 +162,7 @@ impl BlockBehaviour for CoralFanBlock {
|
||||
if is_wall_fan(args.block) {
|
||||
let props = CoralWallFanLikeProperties::from_state_id(args.state_id, args.block);
|
||||
if props.facing.to_block_direction().opposite() == args.direction
|
||||
&& !can_place_at(
|
||||
args.world,
|
||||
args.position,
|
||||
props.facing.to_block_direction().opposite(),
|
||||
)
|
||||
&& !can_place_at(args.world, args.position, props.facing.opposite())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@ -164,15 +172,18 @@ impl BlockBehaviour for CoralFanBlock {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
args.state_id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_default_coral_fan_state_id(block: &Block, waterlogged: bool) -> BlockStateId {
|
||||
let mut props = CoralFanLikeProperties::default(block);
|
||||
props.waterlogged = waterlogged;
|
||||
props.to_state_id(block)
|
||||
}
|
||||
|
||||
fn is_wall_fan(block: &Block) -> bool {
|
||||
block.has_tag(&tag::Block::MINECRAFT_WALL_CORALS)
|
||||
|| block == &Block::DEAD_BRAIN_CORAL_WALL_FAN
|
||||
@ -181,6 +192,7 @@ fn is_wall_fan(block: &Block) -> bool {
|
||||
|| block == &Block::DEAD_HORN_CORAL_WALL_FAN
|
||||
|| block == &Block::DEAD_TUBE_CORAL_WALL_FAN
|
||||
}
|
||||
|
||||
fn get_dead_type(block: &Block) -> Block {
|
||||
if block == &Block::BRAIN_CORAL_FAN {
|
||||
Block::DEAD_BRAIN_CORAL_FAN
|
||||
@ -204,6 +216,7 @@ fn get_dead_type(block: &Block) -> Block {
|
||||
Block::DEAD_TUBE_CORAL_WALL_FAN
|
||||
}
|
||||
}
|
||||
|
||||
fn get_corresponding_wall_fan_type(block: &Block) -> Option<Block> {
|
||||
if block == &Block::TUBE_CORAL_FAN {
|
||||
Some(Block::TUBE_CORAL_WALL_FAN)
|
||||
@ -229,8 +242,9 @@ fn get_corresponding_wall_fan_type(block: &Block) -> Option<Block> {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn can_place_at(world: &dyn BlockAccessor, block_pos: &BlockPos, facing: BlockDirection) -> bool {
|
||||
|
||||
fn can_place_at(world: &dyn BlockAccessor, block_pos: &BlockPos, facing: HorizontalFacing) -> bool {
|
||||
world
|
||||
.get_block_state(&block_pos.offset(facing.to_offset()))
|
||||
.is_side_solid(facing.opposite())
|
||||
.is_side_solid(facing.opposite().to_block_direction())
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::BlockDirection;
|
||||
use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use rand::RngExt;
|
||||
|
||||
@ -14,8 +15,8 @@ pub mod coral_plant;
|
||||
pub async fn scan_for_water(world: &Arc<World>, pos: &BlockPos) -> bool {
|
||||
for direction in BlockDirection::all() {
|
||||
let neighbor_pos = pos.offset(direction.to_offset());
|
||||
let block = world.get_block(&neighbor_pos);
|
||||
if block == &Block::WATER {
|
||||
let block = world.get_fluid(&neighbor_pos);
|
||||
if block.has_tag(&pumpkin_data::tag::Fluid::MINECRAFT_WATER) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
use crate::entity::EntityBase;
|
||||
use pumpkin_data::BlockDirection;
|
||||
use pumpkin_data::HorizontalFacingExt;
|
||||
use pumpkin_data::block_properties::Axis;
|
||||
use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_data::block_properties::DoorHinge;
|
||||
@ -112,14 +111,14 @@ async fn get_hinge(
|
||||
) -> DoorHinge {
|
||||
let top_pos = pos.up();
|
||||
let left_dir = facing.rotate_counter_clockwise();
|
||||
let left_pos = pos.offset(left_dir.to_block_direction().to_offset());
|
||||
let left_pos = pos.offset(left_dir.to_offset());
|
||||
let (left_block, left_state) = world.get_block_and_state(&left_pos);
|
||||
let top_facing = top_pos.offset(facing.to_block_direction().to_offset());
|
||||
let top_facing = top_pos.offset(facing.to_offset());
|
||||
let top_state = world.get_block_state(&top_facing);
|
||||
let right_dir = facing.rotate_clockwise();
|
||||
let right_pos = pos.offset(right_dir.to_block_direction().to_offset());
|
||||
let right_pos = pos.offset(right_dir.to_offset());
|
||||
let (right_block, right_state) = world.get_block_and_state(&right_pos);
|
||||
let top_right = top_pos.offset(facing.to_block_direction().to_offset());
|
||||
let top_right = top_pos.offset(facing.to_offset());
|
||||
let top_right_state = world.get_block_state(&top_right);
|
||||
|
||||
let has_left_door = world
|
||||
@ -139,7 +138,7 @@ async fn get_hinge(
|
||||
|
||||
if (!has_left_door || has_right_door) && score <= 0 {
|
||||
if (!has_right_door || has_left_door) && score >= 0 {
|
||||
let offset = facing.to_block_direction().to_offset();
|
||||
let offset = facing.to_offset();
|
||||
let hit = use_item.cursor_pos;
|
||||
if (offset.x >= 0 || hit.z > 0.5)
|
||||
&& (offset.x <= 0 || hit.z < 0.5)
|
||||
|
||||
@ -3,7 +3,9 @@ use crate::block::GetStateForNeighborUpdateArgs;
|
||||
use crate::block::OnPlaceArgs;
|
||||
use pumpkin_data::BlockDirection;
|
||||
use pumpkin_data::BlockState;
|
||||
use pumpkin_data::HorizontalFacingExt;
|
||||
use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_data::block_properties::HorizontalFacing;
|
||||
use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_data::{Block, tag};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
@ -53,13 +55,17 @@ pub fn compute_fence_state(
|
||||
let other_block_pos = block_pos.offset(direction.to_offset());
|
||||
let (other_block, other_block_state) = world.get_block_and_state(&other_block_pos);
|
||||
|
||||
let connected = connects_to(block, other_block, other_block_state, direction);
|
||||
let connected = connects_to(
|
||||
block,
|
||||
other_block,
|
||||
other_block_state,
|
||||
direction.to_block_direction(),
|
||||
);
|
||||
match direction {
|
||||
BlockDirection::North => fence_props.north = connected,
|
||||
BlockDirection::South => fence_props.south = connected,
|
||||
BlockDirection::West => fence_props.west = connected,
|
||||
BlockDirection::East => fence_props.east = connected,
|
||||
_ => {}
|
||||
HorizontalFacing::North => fence_props.north = connected,
|
||||
HorizontalFacing::South => fence_props.south = connected,
|
||||
HorizontalFacing::West => fence_props.west = connected,
|
||||
HorizontalFacing::East => fence_props.east = connected,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -103,19 +103,17 @@ impl FireBlockBase {
|
||||
}
|
||||
|
||||
let dir = if direction.is_horizontal() {
|
||||
direction.rotate_counter_clockwise()
|
||||
direction
|
||||
.rotate_counter_clockwise()
|
||||
.to_horizontal_axis()
|
||||
.unwrap()
|
||||
} else {
|
||||
BlockDirection::random_horizontal(&mut RandomGenerator::Xoroshiro(
|
||||
Xoroshiro::from_seed(rand::rng().random()),
|
||||
))
|
||||
.to_axis()
|
||||
};
|
||||
NetherPortal::get_new_portal(
|
||||
world,
|
||||
block_pos,
|
||||
dir.to_horizontal_axis()
|
||||
.expect("Direction should be horizontal"),
|
||||
)
|
||||
.is_some()
|
||||
NetherPortal::get_new_portal(world, block_pos, dir).is_some()
|
||||
}
|
||||
|
||||
/// Shared fire collision behavior used by `fire` and `soul_fire`.
|
||||
|
||||
@ -2,7 +2,9 @@ use crate::block::BlockFuture;
|
||||
use crate::block::GetStateForNeighborUpdateArgs;
|
||||
use crate::block::OnPlaceArgs;
|
||||
use pumpkin_data::BlockDirection;
|
||||
use pumpkin_data::HorizontalFacingExt;
|
||||
use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_data::block_properties::HorizontalFacing;
|
||||
use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_data::{Block, tag};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
@ -52,17 +54,16 @@ pub fn compute_pane_state(
|
||||
let (other_block, other_block_state) = world.get_block_and_state(&other_block_pos);
|
||||
|
||||
let connected = other_block == block
|
||||
|| other_block_state.is_side_solid(direction.opposite())
|
||||
|| other_block_state.is_side_solid(direction.opposite().to_block_direction())
|
||||
|| other_block.has_tag(&tag::Block::C_GLASS_PANES)
|
||||
|| other_block == &Block::IRON_BARS
|
||||
|| other_block.has_tag(&tag::Block::MINECRAFT_WALLS);
|
||||
|
||||
match direction {
|
||||
BlockDirection::North => pane_props.north = connected,
|
||||
BlockDirection::South => pane_props.south = connected,
|
||||
BlockDirection::West => pane_props.west = connected,
|
||||
BlockDirection::East => pane_props.east = connected,
|
||||
_ => {}
|
||||
HorizontalFacing::North => pane_props.north = connected,
|
||||
HorizontalFacing::South => pane_props.south = connected,
|
||||
HorizontalFacing::West => pane_props.west = connected,
|
||||
HorizontalFacing::East => pane_props.east = connected,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,9 @@ use crate::block::BlockFuture;
|
||||
use crate::block::GetStateForNeighborUpdateArgs;
|
||||
use crate::block::OnPlaceArgs;
|
||||
use pumpkin_data::BlockDirection;
|
||||
use pumpkin_data::HorizontalFacingExt;
|
||||
use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_data::block_properties::HorizontalFacing;
|
||||
use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_data::{Block, tag};
|
||||
use pumpkin_macros::pumpkin_block;
|
||||
@ -49,16 +51,15 @@ pub fn compute_bars_state(
|
||||
let (other_block, other_block_state) = world.get_block_and_state(&other_block_pos);
|
||||
|
||||
let connected = other_block == block
|
||||
|| other_block_state.is_side_solid(direction.opposite())
|
||||
|| other_block_state.is_side_solid(direction.opposite().to_block_direction())
|
||||
|| other_block.has_tag(&tag::Block::C_GLASS_PANES)
|
||||
|| other_block.has_tag(&tag::Block::MINECRAFT_WALLS);
|
||||
|
||||
match direction {
|
||||
BlockDirection::North => bars_props.north = connected,
|
||||
BlockDirection::South => bars_props.south = connected,
|
||||
BlockDirection::West => bars_props.west = connected,
|
||||
BlockDirection::East => bars_props.east = connected,
|
||||
_ => {}
|
||||
HorizontalFacing::North => bars_props.north = connected,
|
||||
HorizontalFacing::South => bars_props.south = connected,
|
||||
HorizontalFacing::West => bars_props.west = connected,
|
||||
HorizontalFacing::East => bars_props.east = connected,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -45,7 +45,6 @@ impl BlockBehaviour for LadderBlock {
|
||||
}
|
||||
props.facing = dir
|
||||
.opposite()
|
||||
.to_block_direction()
|
||||
.to_horizontal_facing()
|
||||
.expect("Opposite of horizontal direction should be horizontal");
|
||||
return props.to_state_id(args.block);
|
||||
@ -59,7 +58,7 @@ impl BlockBehaviour for LadderBlock {
|
||||
//this won't happen
|
||||
return false;
|
||||
};
|
||||
if can_place_ladder_at(world, args.position, dir) {
|
||||
if can_place_ladder_at(world, args.position, dir.to_block_direction()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,9 +5,10 @@ use crate::block::{
|
||||
};
|
||||
use pumpkin_data::{
|
||||
Block, BlockDirection,
|
||||
block_properties::{BlockProperties, WallTorchLikeProperties, WheatLikeProperties},
|
||||
tag,
|
||||
tag::Taggable,
|
||||
block_properties::{
|
||||
BlockProperties, HorizontalFacing, WallTorchLikeProperties, WheatLikeProperties,
|
||||
},
|
||||
tag::{self, Taggable},
|
||||
};
|
||||
use pumpkin_util::{
|
||||
math::position::BlockPos,
|
||||
@ -37,14 +38,14 @@ impl StemBlock {
|
||||
props.to_state_id(block)
|
||||
}
|
||||
|
||||
fn get_attached_stem(dir: BlockDirection, block: &Block) -> BlockStateId {
|
||||
fn get_attached_stem(dir: HorizontalFacing, block: &Block) -> BlockStateId {
|
||||
let attached_block = match block.id {
|
||||
id if id == Block::PUMPKIN_STEM.id => &Block::ATTACHED_PUMPKIN_STEM,
|
||||
id if id == Block::MELON_STEM.id => &Block::ATTACHED_MELON_STEM,
|
||||
_ => &Block::ATTACHED_MELON_STEM, // Should never happen
|
||||
};
|
||||
let mut props = AttachedStemProperties::default(attached_block);
|
||||
props.facing = dir.to_horizontal_facing().unwrap();
|
||||
props.facing = dir;
|
||||
props.to_state_id(attached_block)
|
||||
}
|
||||
|
||||
|
||||
@ -154,12 +154,9 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
|
||||
fn get_output_level<'a>(&'a self, world: &'a World, pos: BlockPos) -> BlockFuture<'a, u8> {
|
||||
Box::pin(async move {
|
||||
if let Some(blockentity) = world.get_block_entity(&pos)
|
||||
&& blockentity.resource_location() == ComparatorBlockEntity::ID
|
||||
&& let Some(comparator) =
|
||||
blockentity.as_any().downcast_ref::<ComparatorBlockEntity>()
|
||||
{
|
||||
let comparator = blockentity
|
||||
.as_any()
|
||||
.downcast_ref::<ComparatorBlockEntity>()
|
||||
.unwrap();
|
||||
return comparator.output_signal.load(Ordering::Relaxed);
|
||||
}
|
||||
0
|
||||
@ -178,24 +175,25 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
|
||||
return;
|
||||
}
|
||||
let i = self.calculate_output_signal(world, pos, state, block).await;
|
||||
|
||||
let j = RedstoneGateBlock::get_output_level(self, world, pos).await;
|
||||
|
||||
let props = ComparatorLikeProperties::from_state_id(state.id, block);
|
||||
|
||||
if i != j
|
||||
|| props.powered
|
||||
!= RedstoneGateBlock::has_power(self, world, pos, state, block).await
|
||||
{
|
||||
world.schedule_block_tick(
|
||||
block,
|
||||
pos,
|
||||
RedstoneGateBlock::get_update_delay_internal(self, state.id, block),
|
||||
let priority =
|
||||
if RedstoneGateBlock::is_target_not_aligned(self, world, pos, state, block) {
|
||||
TickPriority::High
|
||||
} else {
|
||||
TickPriority::Normal
|
||||
},
|
||||
};
|
||||
|
||||
world.schedule_block_tick(
|
||||
block,
|
||||
pos,
|
||||
RedstoneGateBlock::get_update_delay_internal(self, state.id, block),
|
||||
priority,
|
||||
);
|
||||
}
|
||||
})
|
||||
@ -217,6 +215,7 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
|
||||
self, world, pos, state.id, block, false,
|
||||
)
|
||||
.await;
|
||||
|
||||
if i > j {
|
||||
true
|
||||
} else {
|
||||
@ -244,7 +243,6 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
|
||||
let source_pos = pos.offset(facing.to_offset());
|
||||
let (source_block, source_state) = world.get_block_and_state(&source_pos);
|
||||
|
||||
// Note: .get_comparator_output is assumed to be an async method returning Option<u8>
|
||||
if let Some(pumpkin_block) = world.block_registry.get_pumpkin_block(source_block.id)
|
||||
&& let Some(level) = pumpkin_block
|
||||
.get_comparator_output(GetComparatorOutputArgs {
|
||||
@ -259,25 +257,28 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
|
||||
}
|
||||
|
||||
if redstone_level < 15 && source_state.is_solid_block() {
|
||||
let source_pos = source_pos.offset(facing.to_offset());
|
||||
let (source_block, source_state) = world.get_block_and_state(&source_pos);
|
||||
let deeper_source_pos = source_pos.offset(facing.to_offset());
|
||||
let (deeper_block, deeper_state) = world.get_block_and_state(&deeper_source_pos);
|
||||
|
||||
// Note: self.get_attached_itemframe_level is assumed to be an async method
|
||||
let itemframe_level = Self::get_attached_itemframe_level(world, facing, source_pos);
|
||||
let itemframe_level =
|
||||
Self::get_attached_itemframe_level(world, facing, deeper_source_pos);
|
||||
|
||||
// This is the correct way to handle the async call within the Option
|
||||
let block_level = if let Some(pumpkin_block) =
|
||||
world.block_registry.get_pumpkin_block(source_block.id)
|
||||
world.block_registry.get_pumpkin_block(deeper_block.id)
|
||||
{
|
||||
pumpkin_block
|
||||
.get_comparator_output(GetComparatorOutputArgs {
|
||||
world,
|
||||
block: source_block,
|
||||
state: source_state,
|
||||
position: &source_pos,
|
||||
block: deeper_block,
|
||||
state: deeper_state,
|
||||
position: &deeper_source_pos,
|
||||
})
|
||||
.await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(level) = itemframe_level.max(block_level) {
|
||||
return level;
|
||||
}
|
||||
@ -287,7 +288,7 @@ impl RedstoneGateBlock<ComparatorLikeProperties> for ComparatorBlock {
|
||||
}
|
||||
|
||||
fn get_update_delay_internal(&self, _state_id: BlockStateId, _block: &Block) -> u8 {
|
||||
2
|
||||
2 // Vanilla Delay
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,10 +300,15 @@ impl ComparatorBlock {
|
||||
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::empty())
|
||||
@ -320,12 +326,18 @@ impl ComparatorBlock {
|
||||
block: &Block,
|
||||
) -> u8 {
|
||||
let power = self.get_power(world, pos, state, block).await;
|
||||
if power == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let sub_power = self
|
||||
.get_max_input_level_sides(world, pos, state.id, block, false)
|
||||
.await;
|
||||
if sub_power >= power {
|
||||
|
||||
if sub_power > power {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let props = ComparatorLikeProperties::from_state_id(state.id, block);
|
||||
if props.mode == ModeComparator::Subtract {
|
||||
power - sub_power
|
||||
@ -346,10 +358,10 @@ impl ComparatorBlock {
|
||||
entity.get_entity().entity_type == &EntityType::ITEM_FRAME
|
||||
&& entity.get_entity().get_horizontal_facing() == facing
|
||||
});
|
||||
if let Some(_itemframe) = itemframes.next()
|
||||
&& itemframes.next().is_none()
|
||||
{
|
||||
// TODO itemframe.getComparatorPower()
|
||||
|
||||
if itemframes.next().is_some() && itemframes.next().is_none() {
|
||||
// Vanilla Parity TODO:
|
||||
// Return itemframe.getAnalogOutput() which calculates based on item rotation
|
||||
return Some(1);
|
||||
}
|
||||
None
|
||||
@ -358,22 +370,21 @@ impl ComparatorBlock {
|
||||
async fn update(&self, world: &Arc<World>, pos: BlockPos, state: &BlockState, block: &Block) {
|
||||
let future_level = i32::from(self.calculate_output_signal(world, pos, state, block).await);
|
||||
let mut now_level = 0;
|
||||
|
||||
if let Some(blockentity) = world.get_block_entity(&pos)
|
||||
&& blockentity.resource_location() == ComparatorBlockEntity::ID
|
||||
&& let Some(comparator) = blockentity.as_any().downcast_ref::<ComparatorBlockEntity>()
|
||||
{
|
||||
let comparator = blockentity
|
||||
.as_any()
|
||||
.downcast_ref::<ComparatorBlockEntity>()
|
||||
.unwrap();
|
||||
now_level = i32::from(comparator.output_signal.load(Ordering::Relaxed));
|
||||
comparator
|
||||
.output_signal
|
||||
.store(future_level as u8, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let mut props = ComparatorLikeProperties::from_state_id(state.id, block);
|
||||
if now_level != future_level || props.mode == ModeComparator::Compare {
|
||||
let future_power = self.has_power(world, pos, state, block).await;
|
||||
let now_power = props.powered;
|
||||
|
||||
if now_power && !future_power {
|
||||
props.powered = false;
|
||||
world
|
||||
@ -385,6 +396,7 @@ impl ComparatorBlock {
|
||||
.set_block_state(&pos, props.to_state_id(block), BlockFlags::NOTIFY_LISTENERS)
|
||||
.await;
|
||||
}
|
||||
|
||||
RedstoneGateBlock::update_target(self, world, pos, props.to_state_id(block), block)
|
||||
.await;
|
||||
}
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
use rand::{Rng, RngExt, rng};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::block::blocks::redstone::block_receives_redstone_power;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, NormalUseArgs, OnNeighborUpdateArgs, OnPlaceArgs,
|
||||
OnScheduledTickArgs, PlacedArgs,
|
||||
};
|
||||
use crate::entity::Entity;
|
||||
use crate::entity::EntityBase;
|
||||
use crate::entity::item::ItemEntity;
|
||||
use crate::entity::{Entity, EntityBase};
|
||||
|
||||
use crate::block::entities::dropper::DropperBlockEntity;
|
||||
use crate::block::entities::hopper::HopperBlockEntity;
|
||||
@ -26,9 +29,6 @@ use pumpkin_world::BlockStateId;
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
use pumpkin_world::tick::TickPriority;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
use rand::{Rng, RngExt, rng};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
struct DropperScreenFactory(Arc<dyn Inventory>);
|
||||
|
||||
@ -120,10 +120,12 @@ impl BlockBehaviour for DropperBlock {
|
||||
Box::pin(async move {
|
||||
let powered = block_receives_redstone_power(args.world, args.position).await
|
||||
|| block_receives_redstone_power(args.world, &args.position.up()).await;
|
||||
|
||||
let mut props = DispenserLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
);
|
||||
|
||||
if powered && !props.triggered {
|
||||
args.world
|
||||
.schedule_block_tick(args.block, *args.position, 4, TickPriority::Normal);
|
||||
@ -151,76 +153,79 @@ impl BlockBehaviour for DropperBlock {
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if let Some(block_entity) = args.world.get_block_entity(args.position) {
|
||||
let dropper = block_entity
|
||||
.as_any()
|
||||
.downcast_ref::<DropperBlockEntity>()
|
||||
.unwrap();
|
||||
// Safely extract the dropper instead of unwrap()
|
||||
let Some(dropper) = block_entity.as_any().downcast_ref::<DropperBlockEntity>()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mut item) = dropper.get_random_slot().await {
|
||||
let props = DispenserLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
);
|
||||
if let Some(entity) = args.world.get_block_entity(
|
||||
&args
|
||||
|
||||
let target_pos = args
|
||||
.position
|
||||
.offset(props.facing.to_block_direction().to_offset()),
|
||||
) && let Some(container) = entity.get_inventory()
|
||||
.offset(props.facing.to_block_direction().to_offset());
|
||||
|
||||
if let Some(entity) = args.world.get_block_entity(&target_pos)
|
||||
&& let Some(container) = entity.get_inventory()
|
||||
{
|
||||
// TODO check WorldlyContainer
|
||||
let mut is_full = true;
|
||||
for i in 0..container.size() {
|
||||
let bind = container.get_stack(i).await;
|
||||
let item = bind.lock().await;
|
||||
if item.item_count < item.get_max_stack_size() {
|
||||
is_full = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if is_full {
|
||||
return;
|
||||
}
|
||||
//TODO WorldlyContainer
|
||||
let backup = item.clone();
|
||||
let one_item = item.split(1);
|
||||
|
||||
if HopperBlockEntity::add_one_item(dropper, container.as_ref(), one_item)
|
||||
.await
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
*item = backup;
|
||||
return;
|
||||
}
|
||||
|
||||
// No container found, dispense item into the world
|
||||
let drop_item = item.split(1);
|
||||
let facing = to_normal(props.facing);
|
||||
let mut position = args.position.to_centered_f64().add(&(facing * 0.7));
|
||||
|
||||
position.y -= match props.facing {
|
||||
Facing::Up | Facing::Down => 0.125,
|
||||
_ => 0.15625,
|
||||
};
|
||||
|
||||
let entity = Entity::new(args.world.clone(), position, &EntityType::ITEM);
|
||||
let rd = rng().random::<f64>().mul_add(0.1, 0.2);
|
||||
|
||||
let velocity = Vector3::new(
|
||||
triangle(&mut rng(), facing.x * rd, 0.017_227_5 * 6.),
|
||||
triangle(&mut rng(), 0.2, 0.017_227_5 * 6.),
|
||||
triangle(&mut rng(), facing.z * rd, 0.017_227_5 * 6.),
|
||||
);
|
||||
|
||||
let item_entity = Arc::new(ItemEntity::new_with_velocity(
|
||||
entity, drop_item, velocity, 40,
|
||||
));
|
||||
args.world.spawn_entity(item_entity).await;
|
||||
|
||||
args.world.sync_world_event(
|
||||
WorldEvent::SoundDispenserDispense,
|
||||
*args.position,
|
||||
0,
|
||||
);
|
||||
|
||||
args.world.sync_world_event(
|
||||
WorldEvent::SoundDispenserProjectileLaunch,
|
||||
WorldEvent::ParticlesShootSmoke,
|
||||
*args.position,
|
||||
to_data3d(props.facing),
|
||||
);
|
||||
} else {
|
||||
args.world
|
||||
.sync_world_event(WorldEvent::SoundDispenserFail, *args.position, 0);
|
||||
args.world.sync_world_event(
|
||||
WorldEvent::SoundDispenserDispense,
|
||||
*args.position,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -79,11 +79,7 @@ impl BlockBehaviour for RedstoneTorchBlock {
|
||||
&& can_place_at(world, location, dir.to_block_direction())
|
||||
{
|
||||
let mut torch_props = RWallTorchProps::default(&Block::REDSTONE_WALL_TORCH);
|
||||
torch_props.facing = dir
|
||||
.opposite()
|
||||
.to_block_direction()
|
||||
.to_horizontal_facing()
|
||||
.unwrap();
|
||||
torch_props.facing = dir.opposite().to_horizontal_facing().unwrap();
|
||||
return torch_props.to_state_id(&Block::REDSTONE_WALL_TORCH);
|
||||
}
|
||||
}
|
||||
@ -103,7 +99,7 @@ impl BlockBehaviour for RedstoneTorchBlock {
|
||||
return true;
|
||||
}
|
||||
for dir in BlockDirection::horizontal() {
|
||||
if can_place_at(args.block_accessor, args.position, dir) {
|
||||
if can_place_at(args.block_accessor, args.position, dir.to_block_direction()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::block_properties::{
|
||||
BlockProperties, EastRedstone, NorthRedstone, ObserverLikeProperties,
|
||||
BlockProperties, EastRedstone, HorizontalFacing, NorthRedstone, ObserverLikeProperties,
|
||||
RedstoneWireLikeProperties, RepeaterLikeProperties, SouthRedstone, WestRedstone,
|
||||
};
|
||||
use pumpkin_data::{Block, BlockDirection, BlockState, HorizontalFacingExt};
|
||||
@ -116,7 +116,7 @@ impl BlockBehaviour for RedstoneWireBlock {
|
||||
args.world
|
||||
.replace_with_state_for_neighbor_update(
|
||||
&up_block_pos,
|
||||
direction.opposite(),
|
||||
direction.opposite().to_block_direction(),
|
||||
args.flags,
|
||||
)
|
||||
.await;
|
||||
@ -128,7 +128,7 @@ impl BlockBehaviour for RedstoneWireBlock {
|
||||
args.world
|
||||
.replace_with_state_for_neighbor_update(
|
||||
&down_block_pos,
|
||||
direction.opposite(),
|
||||
direction.opposite().to_block_direction(),
|
||||
args.flags,
|
||||
)
|
||||
.await;
|
||||
@ -201,7 +201,7 @@ impl BlockBehaviour for RedstoneWireBlock {
|
||||
Box::pin(async move {
|
||||
let wire = RedstoneWireProperties::from_state_id(args.state.id, args.block);
|
||||
if args.direction == BlockDirection::Up
|
||||
|| wire.is_side_connected(args.direction.opposite())
|
||||
|| wire.is_side_connected(args.direction.opposite().to_horizontal_facing().unwrap())
|
||||
{
|
||||
wire.power
|
||||
} else {
|
||||
@ -217,7 +217,7 @@ impl BlockBehaviour for RedstoneWireBlock {
|
||||
Box::pin(async move {
|
||||
let wire = RedstoneWireProperties::from_state_id(args.state.id, args.block);
|
||||
if args.direction == BlockDirection::Up
|
||||
|| wire.is_side_connected(args.direction.opposite())
|
||||
|| wire.is_side_connected(args.direction.opposite().to_horizontal_facing().unwrap())
|
||||
{
|
||||
wire.power
|
||||
} else {
|
||||
@ -398,18 +398,17 @@ pub async fn get_regulated_sides(
|
||||
}
|
||||
|
||||
trait RedstoneWireLikePropertiesExt {
|
||||
fn is_side_connected(&self, direction: BlockDirection) -> bool;
|
||||
fn is_side_connected(&self, direction: HorizontalFacing) -> bool;
|
||||
//fn get_connection_type(&self, direction: BlockDirection) -> WireConnection;
|
||||
}
|
||||
|
||||
impl RedstoneWireLikePropertiesExt for RedstoneWireLikeProperties {
|
||||
fn is_side_connected(&self, direction: BlockDirection) -> bool {
|
||||
fn is_side_connected(&self, direction: HorizontalFacing) -> bool {
|
||||
match direction {
|
||||
BlockDirection::North => self.north.to_wire_connection().is_connected(),
|
||||
BlockDirection::South => self.south.to_wire_connection().is_connected(),
|
||||
BlockDirection::East => self.east.to_wire_connection().is_connected(),
|
||||
BlockDirection::West => self.west.to_wire_connection().is_connected(),
|
||||
_ => false,
|
||||
HorizontalFacing::North => self.north.to_wire_connection().is_connected(),
|
||||
HorizontalFacing::South => self.south.to_wire_connection().is_connected(),
|
||||
HorizontalFacing::East => self.east.to_wire_connection().is_connected(),
|
||||
HorizontalFacing::West => self.west.to_wire_connection().is_connected(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ use std::sync::atomic::Ordering;
|
||||
use crate::block::entities::sign::SignBlockEntity;
|
||||
use pumpkin_data::Block;
|
||||
use pumpkin_data::BlockDirection;
|
||||
use pumpkin_data::HorizontalFacingExt;
|
||||
use pumpkin_data::block_properties::EnumVariants;
|
||||
use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_inventory::screen_handler::InventoryPlayer;
|
||||
@ -74,7 +75,7 @@ impl SignBlock {
|
||||
let mut side_direction = None;
|
||||
for direction in BlockDirection::horizontal() {
|
||||
let pos = position.offset(direction.to_offset());
|
||||
if Self::is_valid_support(world, &pos, direction.opposite()) {
|
||||
if Self::is_valid_support(world, &pos, direction.opposite().to_block_direction()) {
|
||||
side_direction = Some(direction);
|
||||
break;
|
||||
}
|
||||
@ -82,7 +83,7 @@ impl SignBlock {
|
||||
|
||||
SupportInfo {
|
||||
above_is_valid,
|
||||
side_direction,
|
||||
side_direction: side_direction.map(|d| d.to_block_direction()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,7 +335,7 @@ impl BlockBehaviour for SignBlock {
|
||||
for d in pumpkin_data::BlockDirection::horizontal() {
|
||||
let wall_pos = args.position.offset(d.to_offset());
|
||||
let (block, state) = args.block_accessor.get_block_and_state(&wall_pos);
|
||||
if state.is_side_solid(d.opposite())
|
||||
if state.is_side_solid(d.opposite().to_block_direction())
|
||||
|| block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_LEAVES)
|
||||
|| block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_SIGNS)
|
||||
{
|
||||
|
||||
@ -74,11 +74,7 @@ impl BlockBehaviour for TorchBlock {
|
||||
}
|
||||
};
|
||||
let mut torch_props = WallTorchProps::default(&wall_block);
|
||||
torch_props.facing = dir
|
||||
.opposite()
|
||||
.to_block_direction()
|
||||
.to_horizontal_facing()
|
||||
.unwrap();
|
||||
torch_props.facing = dir.opposite().to_horizontal_facing().unwrap();
|
||||
return torch_props.to_state_id(&wall_block);
|
||||
}
|
||||
}
|
||||
@ -98,7 +94,7 @@ impl BlockBehaviour for TorchBlock {
|
||||
return true;
|
||||
}
|
||||
for dir in BlockDirection::horizontal() {
|
||||
if can_place_at(args.block_accessor, args.position, dir) {
|
||||
if can_place_at(args.block_accessor, args.position, dir.to_block_direction()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||
use pumpkin_inventory::screen_handler::InventoryPlayer;
|
||||
use pumpkin_protocol::bedrock::client::take_item_actor::CTakeItemActor;
|
||||
use pumpkin_protocol::bedrock::server::actor_event::{ActorEventType, SActorEvent};
|
||||
use pumpkin_protocol::codec::var_ulong::VarULong;
|
||||
use pumpkin_util::GameMode;
|
||||
use pumpkin_util::Hand;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
@ -207,11 +208,11 @@ impl LivingEntity {
|
||||
&CTakeItemEntity::new(
|
||||
item.entity_id.into(),
|
||||
self.entity.entity_id.into(),
|
||||
stack_amount.try_into().unwrap(),
|
||||
VarInt(stack_amount as i32),
|
||||
),
|
||||
&CTakeItemActor::new(
|
||||
item.entity_id.try_into().unwrap(),
|
||||
self.entity.entity_id.try_into().unwrap(),
|
||||
VarULong(item.entity_id as u64),
|
||||
VarULong(self.entity.entity_id as u64),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -63,15 +63,15 @@ use pumpkin_protocol::bedrock::client::container_open::CContainerOpen;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::codec::var_long::VarLong;
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
Animation, CAcknowledgeBlockChange, CActionBar, CAwardStats, CChangeDifficulty,
|
||||
CCloseContainer, CCombatDeath, CCustomPayload, CDisguisedChatMessage, CEntityAnimation,
|
||||
CEntityPositionSync, CGameEvent, CItemCooldown, CMapItemData, COpenScreen, CParticle,
|
||||
CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn,
|
||||
CSetCamera, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem,
|
||||
CSetEquipment, CSetExperience, CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect,
|
||||
CStopSound, CSubtitle, CSystemChatMessage, CTabList, CTitleAnimation, CTitleText, CUnloadChunk,
|
||||
CUpdateMobEffect, CUpdateTime, GameEvent, MapIcon, MapPatch, Metadata, PlayerAction,
|
||||
PlayerInfoFlags, PlayerSpawnData, PreviousMessage, Statistic,
|
||||
Animation, CActionBar, CAwardStats, CChangeDifficulty, CCloseContainer, CCombatDeath,
|
||||
CCustomPayload, CDisguisedChatMessage, CEntityAnimation, CEntityPositionSync, CGameEvent,
|
||||
CItemCooldown, CMapItemData, COpenScreen, CParticle, CPlayerAbilities, CPlayerInfoUpdate,
|
||||
CPlayerPosition, CPlayerSpawnPosition, CRespawn, CSetCamera, CSetContainerContent,
|
||||
CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetEquipment, CSetExperience,
|
||||
CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle,
|
||||
CSystemChatMessage, CTabList, CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect,
|
||||
CUpdateTime, GameEvent, MapIcon, MapPatch, Metadata, PlayerAction, PlayerInfoFlags,
|
||||
PlayerSpawnData, PreviousMessage, Statistic,
|
||||
};
|
||||
use pumpkin_protocol::java::server::play::{
|
||||
SClickSlot, SContainerButtonClick, SRenameItem, SlotActionType,
|
||||
@ -452,7 +452,6 @@ pub struct Player {
|
||||
pub mining: AtomicBool,
|
||||
pub start_mining_time: AtomicI32,
|
||||
pub tick_counter: AtomicI32,
|
||||
pub packet_sequence: AtomicI32,
|
||||
pub mining_pos: Mutex<BlockPos>,
|
||||
pub last_input: AtomicI8,
|
||||
/// A counter for teleport IDs used to track pending teleports.
|
||||
@ -646,7 +645,6 @@ impl Player {
|
||||
open_container: AtomicCell::new(None),
|
||||
open_container_pos: AtomicCell::new(None),
|
||||
tick_counter: AtomicI32::new(0),
|
||||
packet_sequence: AtomicI32::new(-1),
|
||||
start_mining_time: AtomicI32::new(0),
|
||||
last_input: AtomicI8::new(0),
|
||||
carried_item: Mutex::new(None),
|
||||
@ -1859,7 +1857,6 @@ impl Player {
|
||||
.await;
|
||||
}
|
||||
|
||||
// TODO Abstract the chunk sending
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub async fn tick(self: &Arc<Self>, server: &Server) {
|
||||
if let Some(camera_id) = self.camera_target_id.load() {
|
||||
@ -1900,13 +1897,6 @@ impl Player {
|
||||
// return;
|
||||
// }
|
||||
|
||||
let seq = self.packet_sequence.swap(-1, Ordering::Relaxed);
|
||||
if seq != -1 {
|
||||
self.client
|
||||
.send_packet_now(&CAcknowledgeBlockChange::new(seq.into()))
|
||||
.await;
|
||||
}
|
||||
|
||||
// Statistics updates
|
||||
{
|
||||
let mut stats = self.stats.lock().await;
|
||||
|
||||
@ -511,6 +511,11 @@ impl PumpkinServer {
|
||||
match udp_result {
|
||||
Ok((len, client_addr)) => {
|
||||
if len > 0 {
|
||||
let Some(socket) = self.udp_socket.clone() else {
|
||||
error!("UDP socket disappeared during receive");
|
||||
return true;
|
||||
};
|
||||
|
||||
let id = udp_buf[0];
|
||||
let is_online = id & 128 != 0;
|
||||
|
||||
@ -531,7 +536,7 @@ impl PumpkinServer {
|
||||
*master_client_id_counter += 1;
|
||||
|
||||
let new_client = Arc::new(BedrockClient::new(
|
||||
self.udp_socket.as_ref().unwrap().clone(),
|
||||
socket,
|
||||
client_addr,
|
||||
be_clients
|
||||
));
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
CChunkBatchEnd, CChunkBatchStart, CChunkData, CPlayDisconnect,
|
||||
CAcknowledgeBlockChange, CChunkBatchEnd, CChunkBatchStart, CChunkData, CPlayDisconnect,
|
||||
};
|
||||
use pumpkin_world::level::SyncChunk;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||
use std::{io::Write, sync::Arc};
|
||||
|
||||
@ -118,6 +118,8 @@ pub struct JavaClient {
|
||||
pub keep_alive_id: AtomicCell<i64>,
|
||||
/// The last time we sent a keep alive packet.
|
||||
pub last_keep_alive_time: AtomicCell<Instant>,
|
||||
|
||||
pub packet_sequence: AtomicI32,
|
||||
}
|
||||
|
||||
pub enum OutgoingPacketType {
|
||||
@ -173,6 +175,7 @@ impl JavaClient {
|
||||
wait_for_keep_alive: AtomicBool::new(false),
|
||||
keep_alive_id: AtomicCell::new(0),
|
||||
last_keep_alive_time: AtomicCell::new(std::time::Instant::now()),
|
||||
packet_sequence: AtomicI32::new(-1),
|
||||
}
|
||||
}
|
||||
pub async fn set_encryption(
|
||||
@ -272,6 +275,13 @@ impl JavaClient {
|
||||
self.last_keep_alive_time.store(Instant::now());
|
||||
let packet = pumpkin_protocol::java::client::play::CKeepAlive::new(keep_alive_id);
|
||||
self.enqueue_packet(&packet).await;
|
||||
|
||||
let seq = self.packet_sequence.swap(-1, Ordering::Relaxed);
|
||||
if seq != -1 {
|
||||
self
|
||||
.send_packet_now(&CAcknowledgeBlockChange::new(seq.into()))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
// INCOMING PACKETS
|
||||
|
||||
@ -2074,12 +2074,12 @@ impl JavaClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_sequence(&self, player: &Player, sequence: i32) {
|
||||
pub fn update_sequence(&self, _player: &Player, sequence: i32) {
|
||||
if sequence < 0 {
|
||||
error!("Expected packet sequence >= 0");
|
||||
}
|
||||
player.packet_sequence.store(
|
||||
player.packet_sequence.load(Ordering::Relaxed).max(sequence),
|
||||
self.packet_sequence.store(
|
||||
self.packet_sequence.load(Ordering::Relaxed).max(sequence),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::{Block, BlockState, damage::DamageType, entity::EntityType};
|
||||
use pumpkin_data::{Block, BlockState, damage::DamageType, entity::EntityType, fluid::Fluid};
|
||||
use pumpkin_util::math::{boundingbox::BoundingBox, position::BlockPos, vector3::Vector3};
|
||||
use pumpkin_world::chunk::ChunkData;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
@ -27,51 +28,102 @@ impl Explosion {
|
||||
&self,
|
||||
world: &World,
|
||||
) -> FxHashMap<BlockPos, (&'static Block, &'static BlockState)> {
|
||||
// Somethings are not vanilla here but make it way faster
|
||||
let mut map = FxHashMap::default();
|
||||
let random_val = rand::random::<f32>();
|
||||
|
||||
let mut chunk_cache: FxHashMap<
|
||||
pumpkin_util::math::vector2::Vector2<i32>,
|
||||
Option<Arc<ChunkData>>,
|
||||
> = FxHashMap::default();
|
||||
|
||||
for x in 0..16 {
|
||||
for y in 0..16 {
|
||||
'block2: for z in 0..16 {
|
||||
for z in 0..16 {
|
||||
if x > 0 && x < 15 && y > 0 && y < 15 && z > 0 && z < 15 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut x = f64::from(x) / 7.5 - 1.0;
|
||||
let mut y = f64::from(y) / 7.5 - 1.0;
|
||||
let mut z = f64::from(z) / 7.5 - 1.0;
|
||||
let mut dir_x = f64::from(x) / 7.5 - 1.0;
|
||||
let mut dir_y = f64::from(y) / 7.5 - 1.0;
|
||||
let mut dir_z = f64::from(z) / 7.5 - 1.0;
|
||||
|
||||
let sqrt = 1.0 / (x * x + y * y + z * z).sqrt();
|
||||
x *= sqrt;
|
||||
y *= sqrt;
|
||||
z *= sqrt;
|
||||
let length = (dir_x * dir_x + dir_y * dir_y + dir_z * dir_z).sqrt();
|
||||
dir_x /= length;
|
||||
dir_y /= length;
|
||||
dir_z /= length;
|
||||
|
||||
let mut pos_x = self.pos.x;
|
||||
let mut pos_y = self.pos.y + 0.0625;
|
||||
let mut pos_y = self.pos.y;
|
||||
let mut pos_z = self.pos.z;
|
||||
|
||||
let random_val = rand::random::<f32>();
|
||||
let mut h = self.power * random_val.mul_add(0.6, 0.7);
|
||||
|
||||
while h > 0.0 {
|
||||
let block_pos = BlockPos::floored(pos_x, pos_y, pos_z);
|
||||
let (block, state) = world.get_block_and_state(&block_pos);
|
||||
let (_, fluid_state) = world.get_fluid_and_fluid_state(&block_pos);
|
||||
|
||||
// if !world.is_in_build_limit(&block_pos) {
|
||||
// // Pass by reference
|
||||
// continue 'block2;
|
||||
// }
|
||||
if !world.is_in_build_limit(block_pos) {
|
||||
break;
|
||||
}
|
||||
|
||||
let (chunk_pos, relative) = block_pos.chunk_and_chunk_relative_position();
|
||||
|
||||
let chunk_opt = chunk_cache.entry(chunk_pos).or_insert_with(|| {
|
||||
world
|
||||
.level
|
||||
.read_chunk_sync(&chunk_pos, std::clone::Clone::clone)
|
||||
});
|
||||
|
||||
let state_id = if let Some(chunk) = chunk_opt {
|
||||
chunk
|
||||
.section
|
||||
.get_block_absolute_y(
|
||||
relative.x as usize,
|
||||
block_pos.0.y,
|
||||
relative.z as usize,
|
||||
)
|
||||
.unwrap_or(Block::AIR.default_state.id)
|
||||
} else {
|
||||
Block::AIR.default_state.id
|
||||
};
|
||||
|
||||
let (block, state) = BlockState::from_id_with_block(state_id);
|
||||
|
||||
let (_fluid, fluid_state) = Fluid::from_state_id(state_id).map_or_else(
|
||||
|| {
|
||||
let is_waterlogged =
|
||||
block.properties(state_id).is_some_and(|props| {
|
||||
props
|
||||
.to_props()
|
||||
.into_iter()
|
||||
.any(|(k, v)| k == "waterlogged" && v == "true")
|
||||
});
|
||||
|
||||
if is_waterlogged {
|
||||
(&Fluid::FLOWING_WATER, &Fluid::FLOWING_WATER.states[0])
|
||||
} else {
|
||||
(&Fluid::EMPTY, &Fluid::EMPTY.states[0])
|
||||
}
|
||||
},
|
||||
|raw_fluid| {
|
||||
let f = raw_fluid.to_flowing();
|
||||
(f, &f.states[0])
|
||||
},
|
||||
);
|
||||
|
||||
if !state.is_air() || !fluid_state.is_empty {
|
||||
let resistance =
|
||||
fluid_state.blast_resistance.max(block.blast_resistance);
|
||||
h -= resistance * 0.3;
|
||||
|
||||
h -= (resistance + 0.3) * 0.3;
|
||||
|
||||
if h > 0.0 {
|
||||
map.insert(block_pos, (block, state));
|
||||
}
|
||||
}
|
||||
pos_x += x * 0.3;
|
||||
pos_y += y * 0.3;
|
||||
pos_z += z * 0.3;
|
||||
|
||||
pos_x += dir_x * 0.3;
|
||||
pos_y += dir_y * 0.3;
|
||||
pos_z += dir_z * 0.3;
|
||||
h -= 0.225_000_01;
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ use border::Worldborder;
|
||||
use bytes::BufMut;
|
||||
use explosion::Explosion;
|
||||
use pumpkin_config::BasicConfiguration;
|
||||
use pumpkin_data::block_properties::is_air;
|
||||
use pumpkin_data::block_properties::{blocks_movement, is_air};
|
||||
use pumpkin_data::block_rotation::{Mirror, Rotation};
|
||||
use pumpkin_data::chunk_gen_settings::GenerationSettings;
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
@ -73,7 +73,7 @@ use pumpkin_data::{
|
||||
sound_id_remap::remap_sound_id_for_version,
|
||||
world::{RAW, WorldEvent},
|
||||
};
|
||||
use pumpkin_data::{BlockDirection, BlockState, translation};
|
||||
use pumpkin_data::{BlockDirection, BlockState, HorizontalFacingExt, translation};
|
||||
use pumpkin_inventory::crafting::recipe_provider::RecipeProvider;
|
||||
use pumpkin_inventory::screen_handler::InventoryPlayer;
|
||||
use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed};
|
||||
@ -1324,7 +1324,7 @@ impl World {
|
||||
false
|
||||
}
|
||||
|
||||
// FlowableFluid.getVelocity()
|
||||
// FlowingFluid.getFlow()
|
||||
pub fn get_fluid_velocity(
|
||||
&self,
|
||||
pos0: BlockPos,
|
||||
@ -1334,66 +1334,57 @@ impl World {
|
||||
let mut velo = Vector3::default();
|
||||
|
||||
for dir in BlockDirection::horizontal() {
|
||||
let mut amplitude = 0.0;
|
||||
|
||||
let offset = dir.to_offset();
|
||||
|
||||
let pos = pos0.offset(offset);
|
||||
|
||||
let (neighbor_fluid, neighbor_state) = self.get_fluid_and_fluid_state(&pos);
|
||||
|
||||
if neighbor_fluid.matches_type(fluid0) {
|
||||
let mut neighbor_height = neighbor_state.height;
|
||||
let mut amplitude = 0.0;
|
||||
|
||||
if neighbor_height == 0.0 {
|
||||
let block_state_id = self.get_block_state_id(&pos);
|
||||
|
||||
let fluid = Fluid::from_state_id(block_state_id).unwrap_or(&Fluid::EMPTY);
|
||||
|
||||
if fluid.id == Fluid::EMPTY.id {
|
||||
let block = Block::get_raw_id_from_state_id(block_state_id);
|
||||
let block_state = BlockState::from_id(block_state_id);
|
||||
|
||||
let blocks_movement = block_state.is_solid()
|
||||
&& block != Block::COBWEB
|
||||
&& block != Block::BAMBOO_SAPLING;
|
||||
let blocks_movement = blocks_movement(block_state, block);
|
||||
|
||||
if !blocks_movement {
|
||||
let down_pos = pos.down();
|
||||
|
||||
let (down_fluid, down_state) = self.get_fluid_and_fluid_state(&down_pos);
|
||||
|
||||
if down_fluid.matches_type(fluid0) {
|
||||
amplitude = f64::from(state0.height - down_state.height) + 0.888_888_9;
|
||||
neighbor_height = down_state.height;
|
||||
if neighbor_height > 0.0 {
|
||||
amplitude = f64::from(state0.height)
|
||||
- (f64::from(neighbor_height) - 0.888_888_9);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !fluid.matches_type(fluid0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//let state = fluid.get_state(block_state_id);
|
||||
amplitude = f64::from(state0.height - fluid.states[0].height);
|
||||
}
|
||||
|
||||
if amplitude == 0.0 {
|
||||
continue;
|
||||
}
|
||||
} else if neighbor_height > 0.0 {
|
||||
amplitude = f64::from(state0.height) - f64::from(neighbor_height);
|
||||
}
|
||||
|
||||
if amplitude != 0.0 {
|
||||
velo.x += f64::from(offset.x) * amplitude;
|
||||
|
||||
velo.z += f64::from(offset.z) * amplitude;
|
||||
}
|
||||
|
||||
// TODO: FALLING
|
||||
}
|
||||
}
|
||||
|
||||
if state0.falling {
|
||||
for dir in BlockDirection::horizontal() {
|
||||
let pos = pos0.offset(dir.to_offset());
|
||||
|
||||
if self.is_flow_blocked(fluid0.id, pos, dir)
|
||||
|| self.is_flow_blocked(fluid0.id, pos.up(), dir)
|
||||
if self.is_solid_face(fluid0.id, pos, dir.to_block_direction())
|
||||
|| self.is_solid_face(fluid0.id, pos.up(), dir.to_block_direction())
|
||||
{
|
||||
if velo.length_squared() != 0.0 {
|
||||
velo = velo.normalize();
|
||||
}
|
||||
|
||||
velo.y -= 6.0;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1406,9 +1397,8 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
// FlowableFluid.isFlowBlocked()
|
||||
|
||||
fn is_flow_blocked(&self, fluid0_id: u16, pos: BlockPos, direction: BlockDirection) -> bool {
|
||||
// FlowingFluid.isSolidFace()
|
||||
fn is_solid_face(&self, fluid0_id: u16, pos: BlockPos, direction: BlockDirection) -> bool {
|
||||
let id = self.get_block_state_id(&pos);
|
||||
|
||||
let fluid = Fluid::from_state_id(id).unwrap_or(&Fluid::EMPTY);
|
||||
|
||||
@ -52,10 +52,9 @@ impl EndPortal {
|
||||
|
||||
fn is_valid_portal(world: &World, pos: BlockPos) -> bool {
|
||||
for dir in BlockDirection::horizontal() {
|
||||
let facing = dir.to_horizontal_facing().unwrap();
|
||||
let mid_pos = pos.offset_dir(dir.to_offset(), 2);
|
||||
let left_pos = mid_pos.offset_dir(facing.rotate_clockwise().to_offset(), 1);
|
||||
let right_pos = mid_pos.offset_dir(facing.rotate_counter_clockwise().to_offset(), 1);
|
||||
let left_pos = mid_pos.offset_dir(dir.rotate_clockwise().to_offset(), 1);
|
||||
let right_pos = mid_pos.offset_dir(dir.rotate_counter_clockwise().to_offset(), 1);
|
||||
|
||||
let (mid_block, mid_state) = world.get_block_and_state_id(&mid_pos);
|
||||
let (left_block, left_state) = world.get_block_and_state_id(&left_pos);
|
||||
@ -73,6 +72,8 @@ impl EndPortal {
|
||||
let right_properties =
|
||||
EndPortalFrameProperties::from_state_id(right_state, right_block);
|
||||
|
||||
let facing = dir.to_facing();
|
||||
|
||||
if left_properties.facing != facing.opposite()
|
||||
|| mid_properties.facing != facing.opposite()
|
||||
|| right_properties.facing != facing.opposite()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user