package net.minecraft.world.level.block; import com.mojang.serialization.MapCodec; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; import java.util.List; import java.util.Optional; import java.util.function.BiPredicate; import java.util.function.Supplier; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.stats.Stat; import net.minecraft.stats.Stats; import net.minecraft.util.RandomSource; import net.minecraft.world.CompoundContainer; import net.minecraft.world.Container; import net.minecraft.world.Containers; import net.minecraft.world.InteractionResult; import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.animal.Cat; import net.minecraft.world.entity.monster.piglin.PiglinAi; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.ScheduledTickAccess; import net.minecraft.world.level.block.ChestBlock.2.1; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.ChestBlockEntity; import net.minecraft.world.level.block.entity.LidBlockEntity; 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.BlockStateProperties; import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.block.state.properties.ChestType; import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.Fluids; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; public class ChestBlock extends AbstractChestBlock implements SimpleWaterloggedBlock { public static final MapCodec CODEC = simpleCodec(properties -> new ChestBlock(() -> BlockEntityType.CHEST, properties)); public static final EnumProperty FACING = HorizontalDirectionalBlock.FACING; public static final EnumProperty TYPE = BlockStateProperties.CHEST_TYPE; public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final int EVENT_SET_OPEN_COUNT = 1; protected static final int AABB_OFFSET = 1; protected static final int AABB_HEIGHT = 14; protected static final VoxelShape NORTH_AABB = Block.box(1.0, 0.0, 0.0, 15.0, 14.0, 15.0); protected static final VoxelShape SOUTH_AABB = Block.box(1.0, 0.0, 1.0, 15.0, 14.0, 16.0); protected static final VoxelShape WEST_AABB = Block.box(0.0, 0.0, 1.0, 15.0, 14.0, 15.0); protected static final VoxelShape EAST_AABB = Block.box(1.0, 0.0, 1.0, 16.0, 14.0, 15.0); protected static final VoxelShape AABB = Block.box(1.0, 0.0, 1.0, 15.0, 14.0, 15.0); private static final DoubleBlockCombiner.Combiner> CHEST_COMBINER = new DoubleBlockCombiner.Combiner>() { public Optional acceptDouble(ChestBlockEntity chestBlockEntity, ChestBlockEntity chestBlockEntity2) { return Optional.of(new CompoundContainer(chestBlockEntity, chestBlockEntity2)); } public Optional acceptSingle(ChestBlockEntity chestBlockEntity) { return Optional.of(chestBlockEntity); } public Optional acceptNone() { return Optional.empty(); } }; private static final DoubleBlockCombiner.Combiner> MENU_PROVIDER_COMBINER = new DoubleBlockCombiner.Combiner>() { public Optional acceptDouble(ChestBlockEntity chestBlockEntity, ChestBlockEntity chestBlockEntity2) { Container container = new CompoundContainer(chestBlockEntity, chestBlockEntity2); return Optional.of(new 1(this, chestBlockEntity, chestBlockEntity2, container)); } public Optional acceptSingle(ChestBlockEntity chestBlockEntity) { return Optional.of(chestBlockEntity); } public Optional acceptNone() { return Optional.empty(); } }; @Override public MapCodec codec() { return CODEC; } protected ChestBlock(Supplier> blockEntityType, BlockBehaviour.Properties properties) { super(properties, blockEntityType); this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(TYPE, ChestType.SINGLE).setValue(WATERLOGGED, false)); } public static DoubleBlockCombiner.BlockType getBlockType(BlockState state) { ChestType chestType = state.getValue(TYPE); if (chestType == ChestType.SINGLE) { return DoubleBlockCombiner.BlockType.SINGLE; } else { return chestType == ChestType.RIGHT ? DoubleBlockCombiner.BlockType.FIRST : DoubleBlockCombiner.BlockType.SECOND; } } @Override protected RenderShape getRenderShape(BlockState state) { return RenderShape.ENTITYBLOCK_ANIMATED; } @Override protected BlockState updateShape( BlockState state, LevelReader level, ScheduledTickAccess scheduledTickAccess, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random ) { if ((Boolean)state.getValue(WATERLOGGED)) { scheduledTickAccess.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level)); } if (neighborState.is(this) && direction.getAxis().isHorizontal()) { ChestType chestType = neighborState.getValue(TYPE); if (state.getValue(TYPE) == ChestType.SINGLE && chestType != ChestType.SINGLE && state.getValue(FACING) == neighborState.getValue(FACING) && getConnectedDirection(neighborState) == direction.getOpposite()) { return state.setValue(TYPE, chestType.getOpposite()); } } else if (getConnectedDirection(state) == direction) { return state.setValue(TYPE, ChestType.SINGLE); } return super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random); } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { if (state.getValue(TYPE) == ChestType.SINGLE) { return AABB; } else { switch (getConnectedDirection(state)) { case NORTH: default: return NORTH_AABB; case SOUTH: return SOUTH_AABB; case WEST: return WEST_AABB; case EAST: return EAST_AABB; } } } /** * @return the Direction pointing from the given state to its attached double chest */ public static Direction getConnectedDirection(BlockState state) { Direction direction = state.getValue(FACING); return state.getValue(TYPE) == ChestType.LEFT ? direction.getClockWise() : direction.getCounterClockWise(); } @Override public BlockState getStateForPlacement(BlockPlaceContext context) { ChestType chestType = ChestType.SINGLE; Direction direction = context.getHorizontalDirection().getOpposite(); FluidState fluidState = context.getLevel().getFluidState(context.getClickedPos()); boolean bl = context.isSecondaryUseActive(); Direction direction2 = context.getClickedFace(); if (direction2.getAxis().isHorizontal() && bl) { Direction direction3 = this.candidatePartnerFacing(context, direction2.getOpposite()); if (direction3 != null && direction3.getAxis() != direction2.getAxis()) { direction = direction3; chestType = direction3.getCounterClockWise() == direction2.getOpposite() ? ChestType.RIGHT : ChestType.LEFT; } } if (chestType == ChestType.SINGLE && !bl) { if (direction == this.candidatePartnerFacing(context, direction.getClockWise())) { chestType = ChestType.LEFT; } else if (direction == this.candidatePartnerFacing(context, direction.getCounterClockWise())) { chestType = ChestType.RIGHT; } } return this.defaultBlockState().setValue(FACING, direction).setValue(TYPE, chestType).setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER); } @Override protected FluidState getFluidState(BlockState state) { return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); } @Nullable private Direction candidatePartnerFacing(BlockPlaceContext context, Direction direction) { BlockState blockState = context.getLevel().getBlockState(context.getClickedPos().relative(direction)); return blockState.is(this) && blockState.getValue(TYPE) == ChestType.SINGLE ? blockState.getValue(FACING) : null; } @Override protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) { Containers.dropContentsOnDestroy(state, newState, level, pos); super.onRemove(state, level, pos, newState, movedByPiston); } @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { if (level instanceof ServerLevel serverLevel) { MenuProvider menuProvider = this.getMenuProvider(state, level, pos); if (menuProvider != null) { player.openMenu(menuProvider); player.awardStat(this.getOpenChestStat()); PiglinAi.angerNearbyPiglins(serverLevel, player, true); } } return InteractionResult.SUCCESS; } protected Stat getOpenChestStat() { return Stats.CUSTOM.get(Stats.OPEN_CHEST); } public BlockEntityType blockEntityType() { return (BlockEntityType)this.blockEntityType.get(); } @Nullable public static Container getContainer(ChestBlock chest, BlockState state, Level level, BlockPos pos, boolean override) { return (Container)chest.combine(state, level, pos, override).apply(CHEST_COMBINER).orElse(null); } @Override public DoubleBlockCombiner.NeighborCombineResult combine(BlockState state, Level level, BlockPos pos, boolean override) { BiPredicate biPredicate; if (override) { biPredicate = (levelAccessor, blockPos) -> false; } else { biPredicate = ChestBlock::isChestBlockedAt; } return DoubleBlockCombiner.combineWithNeigbour( (BlockEntityType)this.blockEntityType.get(), ChestBlock::getBlockType, ChestBlock::getConnectedDirection, FACING, state, level, pos, biPredicate ); } @Nullable @Override protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { return (MenuProvider)this.combine(state, level, pos, false).apply(MENU_PROVIDER_COMBINER).orElse(null); } public static DoubleBlockCombiner.Combiner opennessCombiner(LidBlockEntity lid) { return new DoubleBlockCombiner.Combiner() { public Float2FloatFunction acceptDouble(ChestBlockEntity first, ChestBlockEntity second) { return f -> Math.max(first.getOpenNess(f), second.getOpenNess(f)); } public Float2FloatFunction acceptSingle(ChestBlockEntity single) { return single::getOpenNess; } public Float2FloatFunction acceptNone() { return lid::getOpenNess; } }; } @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new ChestBlockEntity(pos, state); } @Nullable @Override public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType blockEntityType) { return level.isClientSide ? createTickerHelper(blockEntityType, this.blockEntityType(), ChestBlockEntity::lidAnimateTick) : null; } public static boolean isChestBlockedAt(LevelAccessor level, BlockPos pos) { return isBlockedChestByBlock(level, pos) || isCatSittingOnChest(level, pos); } private static boolean isBlockedChestByBlock(BlockGetter level, BlockPos pos) { BlockPos blockPos = pos.above(); return level.getBlockState(blockPos).isRedstoneConductor(level, blockPos); } private static boolean isCatSittingOnChest(LevelAccessor level, BlockPos pos) { List list = level.getEntitiesOfClass(Cat.class, new AABB(pos.getX(), pos.getY() + 1, pos.getZ(), pos.getX() + 1, pos.getY() + 2, pos.getZ() + 1)); if (!list.isEmpty()) { for (Cat cat : list) { if (cat.isInSittingPose()) { return true; } } } return false; } @Override protected boolean hasAnalogOutputSignal(BlockState state) { return true; } @Override protected int getAnalogOutputSignal(BlockState state, Level level, BlockPos pos) { return AbstractContainerMenu.getRedstoneSignalFromContainer(getContainer(this, state, level, pos, false)); } @Override protected BlockState rotate(BlockState state, Rotation rotation) { return state.setValue(FACING, rotation.rotate(state.getValue(FACING))); } @Override protected BlockState mirror(BlockState state, Mirror mirror) { return state.rotate(mirror.getRotation(state.getValue(FACING))); } @Override protected void createBlockStateDefinition(Builder builder) { builder.add(FACING, TYPE, WATERLOGGED); } @Override protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { return false; } @Override protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { BlockEntity blockEntity = level.getBlockEntity(pos); if (blockEntity instanceof ChestBlockEntity) { ((ChestBlockEntity)blockEntity).recheckOpen(); } } }