Pumpkin/pumpkin/src/block/blocks/doors.rs
2025-07-29 12:19:05 +02:00

295 lines
10 KiB
Rust

use async_trait::async_trait;
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;
use pumpkin_data::block_properties::DoubleBlockHalf;
use pumpkin_data::block_properties::HorizontalFacing;
use pumpkin_data::sound::Sound;
use pumpkin_data::sound::SoundCategory;
use pumpkin_data::tag::RegistryKey;
use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::get_tag_values;
use pumpkin_data::{Block, tag};
use pumpkin_macros::pumpkin_block_from_tag;
use pumpkin_util::math::position::BlockPos;
use pumpkin_world::BlockStateId;
use pumpkin_world::world::BlockAccessor;
use pumpkin_world::world::BlockFlags;
use std::sync::Arc;
use crate::block::BlockBehaviour;
use crate::block::CanPlaceAtArgs;
use crate::block::GetStateForNeighborUpdateArgs;
use crate::block::NormalUseArgs;
use crate::block::OnNeighborUpdateArgs;
use crate::block::OnPlaceArgs;
use crate::block::PlacedArgs;
use crate::block::blocks::redstone::block_receives_redstone_power;
use crate::block::registry::BlockActionResult;
use crate::entity::player::Player;
use pumpkin_protocol::java::server::play::SUseItemOn;
use crate::world::World;
type DoorProperties = pumpkin_data::block_properties::OakDoorLikeProperties;
async fn toggle_door(player: &Player, world: &Arc<World>, block_pos: &BlockPos) {
let (block, block_state) = world.get_block_and_state_id(block_pos).await;
let mut door_props = DoorProperties::from_state_id(block_state, block);
door_props.open = !door_props.open;
let other_half = match door_props.half {
DoubleBlockHalf::Upper => BlockDirection::Down,
DoubleBlockHalf::Lower => BlockDirection::Up,
};
let other_pos = block_pos.offset(other_half.to_offset());
let (other_block, other_state_id) = world.get_block_and_state_id(&other_pos).await;
let mut other_door_props = DoorProperties::from_state_id(other_state_id, other_block);
other_door_props.open = door_props.open;
world
.play_block_sound_expect(
player,
get_sound(block, door_props.open),
SoundCategory::Blocks,
*block_pos,
)
.await;
world
.set_block_state(
block_pos,
door_props.to_state_id(block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
world
.set_block_state(
&other_pos,
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
}
fn can_open_door(block: &Block) -> bool {
if block == &Block::IRON_DOOR {
return false;
}
true
}
// Todo: The sounds should be from BlockSetType
fn get_sound(block: &Block, open: bool) -> Sound {
if open {
if block.is_tagged_with_by_tag(&tag::Block::MINECRAFT_WOODEN_DOORS) {
Sound::BlockWoodenDoorOpen
} else if block == &Block::IRON_DOOR {
Sound::BlockIronDoorOpen
} else {
Sound::BlockCopperDoorOpen
}
} else if block.is_tagged_with_by_tag(&tag::Block::MINECRAFT_WOODEN_DOORS) {
Sound::BlockWoodenDoorClose
} else if block == &Block::IRON_DOOR {
Sound::BlockIronDoorClose
} else {
Sound::BlockCopperDoorClose
}
}
#[allow(clippy::pedantic)]
#[inline]
async fn get_hinge(
world: &World,
pos: &BlockPos,
use_item: &SUseItemOn,
facing: HorizontalFacing,
) -> 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_block, left_state) = world.get_block_and_state(&left_pos).await;
let top_facing = top_pos.offset(facing.to_block_direction().to_offset());
let top_state = world.get_block_state(&top_facing).await;
let right_dir = facing.rotate_clockwise();
let right_pos = pos.offset(right_dir.to_block_direction().to_offset());
let (right_block, right_state) = world.get_block_and_state(&right_pos).await;
let top_right = top_pos.offset(facing.to_block_direction().to_offset());
let top_right_state = world.get_block_state(&top_right).await;
let has_left_door = world
.get_block(&left_pos)
.await
.is_tagged_with_by_tag(&tag::Block::MINECRAFT_DOORS)
&& DoorProperties::from_state_id(left_state.id, left_block).half == DoubleBlockHalf::Lower;
let has_right_door = world
.get_block(&right_pos)
.await
.is_tagged_with_by_tag(&tag::Block::MINECRAFT_DOORS)
&& DoorProperties::from_state_id(right_state.id, right_block).half
== DoubleBlockHalf::Lower;
let score = -(left_state.is_full_cube() as i32) - (top_state.is_full_cube() as i32)
+ right_state.is_full_cube() as i32
+ top_right_state.is_full_cube() as i32;
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 hit = use_item.cursor_pos;
if (offset.x >= 0 || hit.z > 0.5)
&& (offset.x <= 0 || hit.z < 0.5)
&& (offset.z >= 0 || hit.x < 0.5)
&& (offset.z <= 0 || hit.x > 0.5)
{
DoorHinge::Left
} else {
DoorHinge::Right
}
} else {
DoorHinge::Left
}
} else {
DoorHinge::Right
}
}
#[pumpkin_block_from_tag("minecraft:doors")]
pub struct DoorBlock;
#[async_trait]
impl BlockBehaviour for DoorBlock {
async fn on_place(&self, args: OnPlaceArgs<'_>) -> BlockStateId {
let powered = block_receives_redstone_power(args.world, args.position).await
|| block_receives_redstone_power(args.world, &args.position.up()).await;
let direction = args.player.living_entity.entity.get_horizontal_facing();
let hinge = get_hinge(args.world, args.position, args.use_item_on, direction).await;
let mut door_props = DoorProperties::default(args.block);
door_props.half = DoubleBlockHalf::Lower;
door_props.facing = direction;
door_props.hinge = hinge;
door_props.powered = powered;
door_props.open = powered;
door_props.to_state_id(args.block)
}
async fn can_place_at(&self, args: CanPlaceAtArgs<'_>) -> bool {
can_place_at(args.block_accessor, args.position).await
}
async fn placed(&self, args: PlacedArgs<'_>) {
let mut door_props = DoorProperties::from_state_id(args.state_id, args.block);
door_props.half = DoubleBlockHalf::Upper;
args.world
.set_block_state(
&args.position.offset(BlockDirection::Up.to_offset()),
door_props.to_state_id(args.block),
BlockFlags::NOTIFY_ALL | BlockFlags::SKIP_BLOCK_ADDED_CALLBACK,
)
.await;
}
async fn normal_use(&self, args: NormalUseArgs<'_>) -> BlockActionResult {
if !can_open_door(args.block) {
return BlockActionResult::Pass;
}
toggle_door(args.player, args.world, args.position).await;
BlockActionResult::Success
}
async fn on_neighbor_update(&self, args: OnNeighborUpdateArgs<'_>) {
let block_state = args.world.get_block_state(args.position).await;
let mut door_props = DoorProperties::from_state_id(block_state.id, args.block);
let other_half = match door_props.half {
DoubleBlockHalf::Upper => BlockDirection::Down,
DoubleBlockHalf::Lower => BlockDirection::Up,
};
let other_pos = args.position.offset(other_half.to_offset());
let (other_block, other_state_id) = args.world.get_block_and_state_id(&other_pos).await;
let powered = block_receives_redstone_power(args.world, args.position).await
|| block_receives_redstone_power(args.world, &other_pos).await;
if args.block.id == other_block.id && powered != door_props.powered {
let mut other_door_props = DoorProperties::from_state_id(other_state_id, other_block);
door_props.powered = !door_props.powered;
other_door_props.powered = door_props.powered;
if powered != door_props.open {
door_props.open = door_props.powered;
other_door_props.open = other_door_props.powered;
args.world
.play_block_sound(
get_sound(args.block, powered),
SoundCategory::Blocks,
*args.position,
)
.await;
}
args.world
.set_block_state(
args.position,
door_props.to_state_id(args.block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
args.world
.set_block_state(
&other_pos,
other_door_props.to_state_id(other_block),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
}
}
async fn get_state_for_neighbor_update(
&self,
args: GetStateForNeighborUpdateArgs<'_>,
) -> BlockStateId {
let lv = DoorProperties::from_state_id(args.state_id, args.block).half;
if args.direction.to_axis() != Axis::Y
|| (lv == DoubleBlockHalf::Lower) != (args.direction == BlockDirection::Up)
{
if lv == DoubleBlockHalf::Lower
&& args.direction == BlockDirection::Down
&& !can_place_at(args.world, args.position).await
{
return 0;
}
} else if Block::from_state_id(args.neighbor_state_id).id == args.block.id
&& DoorProperties::from_state_id(args.neighbor_state_id, args.block).half != lv
{
let mut new_state = DoorProperties::from_state_id(args.neighbor_state_id, args.block);
new_state.half = lv;
return new_state.to_state_id(args.block);
} else {
return 0;
}
args.state_id
}
}
async fn can_place_at(world: &dyn BlockAccessor, block_pos: &BlockPos) -> bool {
world.get_block_state(&block_pos.up()).await.replaceable()
&& world
.get_block_state(&block_pos.down())
.await
.is_side_solid(BlockDirection::Up)
}