package net.minecraft.world.level.block; import com.mojang.serialization.MapCodec; import java.util.Map; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.component.DataComponents; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.stats.Stats; import net.minecraft.tags.ItemTags; import net.minecraft.util.RandomSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.LecternBlockEntity; 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.EnumProperty; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEvent.Context; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; import net.minecraft.world.level.redstone.Orientation; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; public class LecternBlock extends BaseEntityBlock { public static final MapCodec CODEC = simpleCodec(LecternBlock::new); public static final EnumProperty FACING = HorizontalDirectionalBlock.FACING; public static final BooleanProperty POWERED = BlockStateProperties.POWERED; public static final BooleanProperty HAS_BOOK = BlockStateProperties.HAS_BOOK; private static final VoxelShape SHAPE_COLLISION = Shapes.or(Block.column(16.0, 0.0, 2.0), Block.column(8.0, 2.0, 14.0)); private static final Map SHAPES = Shapes.rotateHorizontal( Shapes.or( Block.boxZ(16.0, 10.0, 14.0, 1.0, 5.333333), Block.boxZ(16.0, 12.0, 16.0, 5.333333, 9.666667), Block.boxZ(16.0, 14.0, 18.0, 9.666667, 14.0), SHAPE_COLLISION ) ); private static final int PAGE_CHANGE_IMPULSE_TICKS = 2; @Override public MapCodec codec() { return CODEC; } protected LecternBlock(BlockBehaviour.Properties properties) { super(properties); this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(POWERED, false).setValue(HAS_BOOK, false)); } @Override protected VoxelShape getOcclusionShape(BlockState state) { return SHAPE_COLLISION; } @Override protected boolean useShapeForLightOcclusion(BlockState state) { return true; } @Override public BlockState getStateForPlacement(BlockPlaceContext context) { Level level = context.getLevel(); ItemStack itemStack = context.getItemInHand(); Player player = context.getPlayer(); boolean bl = false; if (!level.isClientSide && player != null && player.canUseGameMasterBlocks()) { CustomData customData = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY); if (customData.contains("Book")) { bl = true; } } return this.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite()).setValue(HAS_BOOK, bl); } @Override protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return SHAPE_COLLISION; } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return (VoxelShape)SHAPES.get(state.getValue(FACING)); } @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, POWERED, HAS_BOOK); } @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new LecternBlockEntity(pos, state); } public static boolean tryPlaceBook(@Nullable LivingEntity entity, Level level, BlockPos pos, BlockState state, ItemStack stack) { if (!(Boolean)state.getValue(HAS_BOOK)) { if (!level.isClientSide) { placeBook(entity, level, pos, state, stack); } return true; } else { return false; } } private static void placeBook(@Nullable LivingEntity entity, Level level, BlockPos pos, BlockState state, ItemStack stack) { if (level.getBlockEntity(pos) instanceof LecternBlockEntity lecternBlockEntity) { lecternBlockEntity.setBook(stack.consumeAndReturn(1, entity)); resetBookState(entity, level, pos, state, true); level.playSound(null, pos, SoundEvents.BOOK_PUT, SoundSource.BLOCKS, 1.0F, 1.0F); } } public static void resetBookState(@Nullable Entity entity, Level level, BlockPos pos, BlockState state, boolean hasBook) { BlockState blockState = state.setValue(POWERED, false).setValue(HAS_BOOK, hasBook); level.setBlock(pos, blockState, 3); level.gameEvent(GameEvent.BLOCK_CHANGE, pos, Context.of(entity, blockState)); updateBelow(level, pos, state); } public static void signalPageChange(Level level, BlockPos pos, BlockState state) { changePowered(level, pos, state, true); level.scheduleTick(pos, state.getBlock(), 2); level.levelEvent(1043, pos, 0); } private static void changePowered(Level level, BlockPos pos, BlockState state, boolean powered) { level.setBlock(pos, state.setValue(POWERED, powered), 3); updateBelow(level, pos, state); } private static void updateBelow(Level level, BlockPos pos, BlockState state) { Orientation orientation = ExperimentalRedstoneUtils.initialOrientation(level, ((Direction)state.getValue(FACING)).getOpposite(), Direction.UP); level.updateNeighborsAt(pos.below(), state.getBlock(), orientation); } @Override protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { changePowered(level, pos, state, false); } @Override protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) { if ((Boolean)state.getValue(POWERED)) { updateBelow(level, pos, state); } } @Override protected boolean isSignalSource(BlockState state) { return true; } @Override protected int getSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) { return state.getValue(POWERED) ? 15 : 0; } @Override protected int getDirectSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) { return direction == Direction.UP && state.getValue(POWERED) ? 15 : 0; } @Override protected boolean hasAnalogOutputSignal(BlockState state) { return true; } @Override protected int getAnalogOutputSignal(BlockState state, Level level, BlockPos pos) { if ((Boolean)state.getValue(HAS_BOOK)) { BlockEntity blockEntity = level.getBlockEntity(pos); if (blockEntity instanceof LecternBlockEntity) { return ((LecternBlockEntity)blockEntity).getRedstoneSignal(); } } return 0; } @Override protected InteractionResult useItemOn( ItemStack stack, BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hitResult ) { if ((Boolean)state.getValue(HAS_BOOK)) { return InteractionResult.TRY_WITH_EMPTY_HAND; } else if (stack.is(ItemTags.LECTERN_BOOKS)) { return (InteractionResult)(tryPlaceBook(player, level, pos, state, stack) ? InteractionResult.SUCCESS : InteractionResult.PASS); } else { return (InteractionResult)(stack.isEmpty() && hand == InteractionHand.MAIN_HAND ? InteractionResult.PASS : InteractionResult.TRY_WITH_EMPTY_HAND); } } @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { if ((Boolean)state.getValue(HAS_BOOK)) { if (!level.isClientSide) { this.openScreen(level, pos, player); } return InteractionResult.SUCCESS; } else { return InteractionResult.CONSUME; } } @Nullable @Override protected MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { return !state.getValue(HAS_BOOK) ? null : super.getMenuProvider(state, level, pos); } private void openScreen(Level level, BlockPos pos, Player player) { BlockEntity blockEntity = level.getBlockEntity(pos); if (blockEntity instanceof LecternBlockEntity) { player.openMenu((LecternBlockEntity)blockEntity); player.awardStat(Stats.INTERACT_WITH_LECTERN); } } @Override protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { return false; } }