minecraft-src/net/minecraft/world/level/block/BedBlock.java
2025-07-04 03:45:38 +03:00

348 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.DoubleBlockCombiner.BlockType;
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.Builder;
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 BlockType getBlockType(BlockState state) {
BedPart bedPart = state.getValue(PART);
return bedPart == BedPart.HEAD ? BlockType.FIRST : 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(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()}};
}
}