347 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.world.level.block;
 | |
| 
 | |
| import com.mojang.math.OctahedralGroup;
 | |
| import com.mojang.math.Quadrant;
 | |
| import com.mojang.serialization.MapCodec;
 | |
| import com.mojang.serialization.codecs.RecordCodecBuilder;
 | |
| import java.util.List;
 | |
| import java.util.Map;
 | |
| import java.util.Optional;
 | |
| import net.minecraft.Util;
 | |
| import net.minecraft.core.BlockPos;
 | |
| import net.minecraft.core.Direction;
 | |
| import net.minecraft.network.chat.Component;
 | |
| import net.minecraft.util.Mth;
 | |
| import net.minecraft.util.RandomSource;
 | |
| import net.minecraft.world.InteractionResult;
 | |
| import net.minecraft.world.entity.Entity;
 | |
| import net.minecraft.world.entity.EntityType;
 | |
| import net.minecraft.world.entity.LivingEntity;
 | |
| import net.minecraft.world.entity.npc.Villager;
 | |
| import net.minecraft.world.entity.player.Player;
 | |
| import net.minecraft.world.entity.vehicle.DismountHelper;
 | |
| import net.minecraft.world.item.DyeColor;
 | |
| import net.minecraft.world.item.ItemStack;
 | |
| import net.minecraft.world.item.context.BlockPlaceContext;
 | |
| import net.minecraft.world.level.BlockGetter;
 | |
| import net.minecraft.world.level.CollisionGetter;
 | |
| import net.minecraft.world.level.Level;
 | |
| import net.minecraft.world.level.LevelReader;
 | |
| import net.minecraft.world.level.ScheduledTickAccess;
 | |
| import net.minecraft.world.level.block.entity.BedBlockEntity;
 | |
| import net.minecraft.world.level.block.entity.BlockEntity;
 | |
| import net.minecraft.world.level.block.state.BlockBehaviour;
 | |
| import net.minecraft.world.level.block.state.BlockState;
 | |
| import net.minecraft.world.level.block.state.StateDefinition;
 | |
| import net.minecraft.world.level.block.state.properties.BedPart;
 | |
| import net.minecraft.world.level.block.state.properties.BlockStateProperties;
 | |
| import net.minecraft.world.level.block.state.properties.BooleanProperty;
 | |
| import net.minecraft.world.level.block.state.properties.EnumProperty;
 | |
| import net.minecraft.world.level.pathfinder.PathComputationType;
 | |
| import net.minecraft.world.phys.AABB;
 | |
| import net.minecraft.world.phys.BlockHitResult;
 | |
| import net.minecraft.world.phys.Vec3;
 | |
| import net.minecraft.world.phys.shapes.CollisionContext;
 | |
| import net.minecraft.world.phys.shapes.Shapes;
 | |
| import net.minecraft.world.phys.shapes.VoxelShape;
 | |
| import org.apache.commons.lang3.ArrayUtils;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| 
 | |
| public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock {
 | |
| 	public static final MapCodec<BedBlock> CODEC = RecordCodecBuilder.mapCodec(
 | |
| 		instance -> instance.group(DyeColor.CODEC.fieldOf("color").forGetter(BedBlock::getColor), propertiesCodec()).apply(instance, BedBlock::new)
 | |
| 	);
 | |
| 	public static final EnumProperty<BedPart> PART = BlockStateProperties.BED_PART;
 | |
| 	public static final BooleanProperty OCCUPIED = BlockStateProperties.OCCUPIED;
 | |
| 	private static final Map<Direction, VoxelShape> SHAPES = Util.make(() -> {
 | |
| 		VoxelShape voxelShape = Block.box(0.0, 0.0, 0.0, 3.0, 3.0, 3.0);
 | |
| 		VoxelShape voxelShape2 = Shapes.rotate(voxelShape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R90));
 | |
| 		return Shapes.rotateHorizontal(Shapes.or(Block.column(16.0, 3.0, 9.0), voxelShape, voxelShape2));
 | |
| 	});
 | |
| 	private final DyeColor color;
 | |
| 
 | |
| 	@Override
 | |
| 	public MapCodec<BedBlock> codec() {
 | |
| 		return CODEC;
 | |
| 	}
 | |
| 
 | |
| 	public BedBlock(DyeColor color, BlockBehaviour.Properties properties) {
 | |
| 		super(properties);
 | |
| 		this.color = color;
 | |
| 		this.registerDefaultState(this.stateDefinition.any().setValue(PART, BedPart.FOOT).setValue(OCCUPIED, false));
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public static Direction getBedOrientation(BlockGetter level, BlockPos pos) {
 | |
| 		BlockState blockState = level.getBlockState(pos);
 | |
| 		return blockState.getBlock() instanceof BedBlock ? blockState.getValue(FACING) : null;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
 | |
| 		if (level.isClientSide) {
 | |
| 			return InteractionResult.SUCCESS_SERVER;
 | |
| 		} else {
 | |
| 			if (state.getValue(PART) != BedPart.HEAD) {
 | |
| 				pos = pos.relative(state.getValue(FACING));
 | |
| 				state = level.getBlockState(pos);
 | |
| 				if (!state.is(this)) {
 | |
| 					return InteractionResult.CONSUME;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (!canSetSpawn(level)) {
 | |
| 				level.removeBlock(pos, false);
 | |
| 				BlockPos blockPos = pos.relative(((Direction)state.getValue(FACING)).getOpposite());
 | |
| 				if (level.getBlockState(blockPos).is(this)) {
 | |
| 					level.removeBlock(blockPos, false);
 | |
| 				}
 | |
| 
 | |
| 				Vec3 vec3 = pos.getCenter();
 | |
| 				level.explode(null, level.damageSources().badRespawnPointExplosion(vec3), null, vec3, 5.0F, true, Level.ExplosionInteraction.BLOCK);
 | |
| 				return InteractionResult.SUCCESS_SERVER;
 | |
| 			} else if ((Boolean)state.getValue(OCCUPIED)) {
 | |
| 				if (!this.kickVillagerOutOfBed(level, pos)) {
 | |
| 					player.displayClientMessage(Component.translatable("block.minecraft.bed.occupied"), true);
 | |
| 				}
 | |
| 
 | |
| 				return InteractionResult.SUCCESS_SERVER;
 | |
| 			} else {
 | |
| 				player.startSleepInBed(pos).ifLeft(bedSleepingProblem -> {
 | |
| 					if (bedSleepingProblem.getMessage() != null) {
 | |
| 						player.displayClientMessage(bedSleepingProblem.getMessage(), true);
 | |
| 					}
 | |
| 				});
 | |
| 				return InteractionResult.SUCCESS_SERVER;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public static boolean canSetSpawn(Level level) {
 | |
| 		return level.dimensionType().bedWorks();
 | |
| 	}
 | |
| 
 | |
| 	private boolean kickVillagerOutOfBed(Level level, BlockPos pos) {
 | |
| 		List<Villager> list = level.getEntitiesOfClass(Villager.class, new AABB(pos), LivingEntity::isSleeping);
 | |
| 		if (list.isEmpty()) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			((Villager)list.get(0)).stopSleeping();
 | |
| 			return true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, double fallDistance) {
 | |
| 		super.fallOn(level, state, pos, entity, fallDistance * 0.5);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void updateEntityMovementAfterFallOn(BlockGetter level, Entity entity) {
 | |
| 		if (entity.isSuppressingBounce()) {
 | |
| 			super.updateEntityMovementAfterFallOn(level, entity);
 | |
| 		} else {
 | |
| 			this.bounceUp(entity);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void bounceUp(Entity entity) {
 | |
| 		Vec3 vec3 = entity.getDeltaMovement();
 | |
| 		if (vec3.y < 0.0) {
 | |
| 			double d = entity instanceof LivingEntity ? 1.0 : 0.8;
 | |
| 			entity.setDeltaMovement(vec3.x, -vec3.y * 0.66F * d, vec3.z);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected BlockState updateShape(
 | |
| 		BlockState state,
 | |
| 		LevelReader level,
 | |
| 		ScheduledTickAccess scheduledTickAccess,
 | |
| 		BlockPos pos,
 | |
| 		Direction direction,
 | |
| 		BlockPos neighborPos,
 | |
| 		BlockState neighborState,
 | |
| 		RandomSource random
 | |
| 	) {
 | |
| 		if (direction == getNeighbourDirection(state.getValue(PART), state.getValue(FACING))) {
 | |
| 			return neighborState.is(this) && neighborState.getValue(PART) != state.getValue(PART)
 | |
| 				? state.setValue(OCCUPIED, (Boolean)neighborState.getValue(OCCUPIED))
 | |
| 				: Blocks.AIR.defaultBlockState();
 | |
| 		} else {
 | |
| 			return super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Given a bed part and the direction it's facing, find the direction to move to get the other bed part
 | |
| 	 */
 | |
| 	private static Direction getNeighbourDirection(BedPart part, Direction direction) {
 | |
| 		return part == BedPart.FOOT ? direction : direction.getOpposite();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) {
 | |
| 		if (!level.isClientSide && player.preventsBlockDrops()) {
 | |
| 			BedPart bedPart = state.getValue(PART);
 | |
| 			if (bedPart == BedPart.FOOT) {
 | |
| 				BlockPos blockPos = pos.relative(getNeighbourDirection(bedPart, state.getValue(FACING)));
 | |
| 				BlockState blockState = level.getBlockState(blockPos);
 | |
| 				if (blockState.is(this) && blockState.getValue(PART) == BedPart.HEAD) {
 | |
| 					level.setBlock(blockPos, Blocks.AIR.defaultBlockState(), 35);
 | |
| 					level.levelEvent(player, 2001, blockPos, Block.getId(blockState));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return super.playerWillDestroy(level, pos, state, player);
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	@Override
 | |
| 	public BlockState getStateForPlacement(BlockPlaceContext context) {
 | |
| 		Direction direction = context.getHorizontalDirection();
 | |
| 		BlockPos blockPos = context.getClickedPos();
 | |
| 		BlockPos blockPos2 = blockPos.relative(direction);
 | |
| 		Level level = context.getLevel();
 | |
| 		return level.getBlockState(blockPos2).canBeReplaced(context) && level.getWorldBorder().isWithinBounds(blockPos2)
 | |
| 			? this.defaultBlockState().setValue(FACING, direction)
 | |
| 			: null;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
 | |
| 		return (VoxelShape)SHAPES.get(getConnectedDirection(state).getOpposite());
 | |
| 	}
 | |
| 
 | |
| 	public static Direction getConnectedDirection(BlockState state) {
 | |
| 		Direction direction = state.getValue(FACING);
 | |
| 		return state.getValue(PART) == BedPart.HEAD ? direction.getOpposite() : direction;
 | |
| 	}
 | |
| 
 | |
| 	public static DoubleBlockCombiner.BlockType getBlockType(BlockState state) {
 | |
| 		BedPart bedPart = state.getValue(PART);
 | |
| 		return bedPart == BedPart.HEAD ? DoubleBlockCombiner.BlockType.FIRST : DoubleBlockCombiner.BlockType.SECOND;
 | |
| 	}
 | |
| 
 | |
| 	private static boolean isBunkBed(BlockGetter level, BlockPos pos) {
 | |
| 		return level.getBlockState(pos.below()).getBlock() instanceof BedBlock;
 | |
| 	}
 | |
| 
 | |
| 	public static Optional<Vec3> findStandUpPosition(EntityType<?> entityType, CollisionGetter collisionGetter, BlockPos pos, Direction direction, float yRot) {
 | |
| 		Direction direction2 = direction.getClockWise();
 | |
| 		Direction direction3 = direction2.isFacingAngle(yRot) ? direction2.getOpposite() : direction2;
 | |
| 		if (isBunkBed(collisionGetter, pos)) {
 | |
| 			return findBunkBedStandUpPosition(entityType, collisionGetter, pos, direction, direction3);
 | |
| 		} else {
 | |
| 			int[][] is = bedStandUpOffsets(direction, direction3);
 | |
| 			Optional<Vec3> optional = findStandUpPositionAtOffset(entityType, collisionGetter, pos, is, true);
 | |
| 			return optional.isPresent() ? optional : findStandUpPositionAtOffset(entityType, collisionGetter, pos, is, false);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static Optional<Vec3> findBunkBedStandUpPosition(
 | |
| 		EntityType<?> entityType, CollisionGetter collisionGetter, BlockPos pos, Direction stateFacing, Direction entityFacing
 | |
| 	) {
 | |
| 		int[][] is = bedSurroundStandUpOffsets(stateFacing, entityFacing);
 | |
| 		Optional<Vec3> optional = findStandUpPositionAtOffset(entityType, collisionGetter, pos, is, true);
 | |
| 		if (optional.isPresent()) {
 | |
| 			return optional;
 | |
| 		} else {
 | |
| 			BlockPos blockPos = pos.below();
 | |
| 			Optional<Vec3> optional2 = findStandUpPositionAtOffset(entityType, collisionGetter, blockPos, is, true);
 | |
| 			if (optional2.isPresent()) {
 | |
| 				return optional2;
 | |
| 			} else {
 | |
| 				int[][] js = bedAboveStandUpOffsets(stateFacing);
 | |
| 				Optional<Vec3> optional3 = findStandUpPositionAtOffset(entityType, collisionGetter, pos, js, true);
 | |
| 				if (optional3.isPresent()) {
 | |
| 					return optional3;
 | |
| 				} else {
 | |
| 					Optional<Vec3> optional4 = findStandUpPositionAtOffset(entityType, collisionGetter, pos, is, false);
 | |
| 					if (optional4.isPresent()) {
 | |
| 						return optional4;
 | |
| 					} else {
 | |
| 						Optional<Vec3> optional5 = findStandUpPositionAtOffset(entityType, collisionGetter, blockPos, is, false);
 | |
| 						return optional5.isPresent() ? optional5 : findStandUpPositionAtOffset(entityType, collisionGetter, pos, js, false);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static Optional<Vec3> findStandUpPositionAtOffset(
 | |
| 		EntityType<?> entityType, CollisionGetter collisionGetter, BlockPos pos, int[][] offsets, boolean simulate
 | |
| 	) {
 | |
| 		BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
 | |
| 
 | |
| 		for (int[] is : offsets) {
 | |
| 			mutableBlockPos.set(pos.getX() + is[0], pos.getY(), pos.getZ() + is[1]);
 | |
| 			Vec3 vec3 = DismountHelper.findSafeDismountLocation(entityType, collisionGetter, mutableBlockPos, simulate);
 | |
| 			if (vec3 != null) {
 | |
| 				return Optional.of(vec3);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return Optional.empty();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
 | |
| 		builder.add(FACING, PART, OCCUPIED);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
 | |
| 		return new BedBlockEntity(pos, state, this.color);
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
 | |
| 		super.setPlacedBy(level, pos, state, placer, stack);
 | |
| 		if (!level.isClientSide) {
 | |
| 			BlockPos blockPos = pos.relative(state.getValue(FACING));
 | |
| 			level.setBlock(blockPos, state.setValue(PART, BedPart.HEAD), 3);
 | |
| 			level.updateNeighborsAt(pos, Blocks.AIR);
 | |
| 			state.updateNeighbourShapes(level, pos, 3);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public DyeColor getColor() {
 | |
| 		return this.color;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected long getSeed(BlockState state, BlockPos pos) {
 | |
| 		BlockPos blockPos = pos.relative(state.getValue(FACING), state.getValue(PART) == BedPart.HEAD ? 0 : 1);
 | |
| 		return Mth.getSeed(blockPos.getX(), pos.getY(), blockPos.getZ());
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	private static int[][] bedStandUpOffsets(Direction firstDir, Direction secondDir) {
 | |
| 		return ArrayUtils.addAll((int[][])bedSurroundStandUpOffsets(firstDir, secondDir), (int[][])bedAboveStandUpOffsets(firstDir));
 | |
| 	}
 | |
| 
 | |
| 	private static int[][] bedSurroundStandUpOffsets(Direction firstDir, Direction secondDir) {
 | |
| 		return new int[][]{
 | |
| 			{secondDir.getStepX(), secondDir.getStepZ()},
 | |
| 			{secondDir.getStepX() - firstDir.getStepX(), secondDir.getStepZ() - firstDir.getStepZ()},
 | |
| 			{secondDir.getStepX() - firstDir.getStepX() * 2, secondDir.getStepZ() - firstDir.getStepZ() * 2},
 | |
| 			{-firstDir.getStepX() * 2, -firstDir.getStepZ() * 2},
 | |
| 			{-secondDir.getStepX() - firstDir.getStepX() * 2, -secondDir.getStepZ() - firstDir.getStepZ() * 2},
 | |
| 			{-secondDir.getStepX() - firstDir.getStepX(), -secondDir.getStepZ() - firstDir.getStepZ()},
 | |
| 			{-secondDir.getStepX(), -secondDir.getStepZ()},
 | |
| 			{-secondDir.getStepX() + firstDir.getStepX(), -secondDir.getStepZ() + firstDir.getStepZ()},
 | |
| 			{firstDir.getStepX(), firstDir.getStepZ()},
 | |
| 			{secondDir.getStepX() + firstDir.getStepX(), secondDir.getStepZ() + firstDir.getStepZ()}
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	private static int[][] bedAboveStandUpOffsets(Direction dir) {
 | |
| 		return new int[][]{{0, 0}, {-dir.getStepX(), -dir.getStepZ()}};
 | |
| 	}
 | |
| }
 |