/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.mojang.datafixers.kinds.App * com.mojang.datafixers.kinds.Applicative * com.mojang.serialization.MapCodec * com.mojang.serialization.codecs.RecordCodecBuilder * it.unimi.dsi.fastutil.floats.Float2FloatFunction * org.jspecify.annotations.Nullable */ package net.minecraft.world.level.block; import com.mojang.datafixers.kinds.App; import com.mojang.datafixers.kinds.Applicative; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; import java.util.List; import java.util.Map; 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.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; 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.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ChestMenu; 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.AbstractChestBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.DoubleBlockCombiner; import net.minecraft.world.level.block.HorizontalDirectionalBlock; import net.minecraft.world.level.block.Mirror; import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.SimpleWaterloggedBlock; 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; 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.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jspecify.annotations.Nullable; public class ChestBlock extends AbstractChestBlock implements SimpleWaterloggedBlock { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(i -> i.group((App)BuiltInRegistries.SOUND_EVENT.byNameCodec().fieldOf("open_sound").forGetter(ChestBlock::getOpenChestSound), (App)BuiltInRegistries.SOUND_EVENT.byNameCodec().fieldOf("close_sound").forGetter(ChestBlock::getCloseChestSound), ChestBlock.propertiesCodec()).apply((Applicative)i, (openSound, closeSound, p) -> new ChestBlock(() -> BlockEntityType.CHEST, (SoundEvent)openSound, (SoundEvent)closeSound, (BlockBehaviour.Properties)p))); 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; private static final VoxelShape SHAPE = Block.column(14.0, 0.0, 14.0); private static final Map HALF_SHAPES = Shapes.rotateHorizontal(Block.boxZ(14.0, 0.0, 14.0, 0.0, 15.0)); private final SoundEvent openSound; private final SoundEvent closeSound; private static final DoubleBlockCombiner.Combiner> CHEST_COMBINER = new DoubleBlockCombiner.Combiner>(){ @Override public Optional acceptDouble(ChestBlockEntity first, ChestBlockEntity second) { return Optional.of(new CompoundContainer(first, second)); } @Override public Optional acceptSingle(ChestBlockEntity single) { return Optional.of(single); } @Override public Optional acceptNone() { return Optional.empty(); } }; private static final DoubleBlockCombiner.Combiner> MENU_PROVIDER_COMBINER = new DoubleBlockCombiner.Combiner>(){ @Override public Optional acceptDouble(final ChestBlockEntity first, final ChestBlockEntity second) { final CompoundContainer container = new CompoundContainer(first, second); return Optional.of(new MenuProvider(){ @Override public @Nullable AbstractContainerMenu createMenu(int containerId, Inventory inventory, Player player) { if (first.canOpen(player) && second.canOpen(player)) { first.unpackLootTable(inventory.player); second.unpackLootTable(inventory.player); return ChestMenu.sixRows(containerId, inventory, container); } return null; } @Override public Component getDisplayName() { if (first.hasCustomName()) { return first.getDisplayName(); } if (second.hasCustomName()) { return second.getDisplayName(); } return Component.translatable("container.chestDouble"); } }); } @Override public Optional acceptSingle(ChestBlockEntity single) { return Optional.of(single); } @Override public Optional acceptNone() { return Optional.empty(); } }; @Override public MapCodec codec() { return CODEC; } protected ChestBlock(Supplier> blockEntityType, SoundEvent openSound, SoundEvent closeSound, BlockBehaviour.Properties properties) { super(properties, blockEntityType); this.openSound = openSound; this.closeSound = closeSound; this.registerDefaultState((BlockState)((BlockState)((BlockState)((BlockState)this.stateDefinition.any()).setValue(FACING, Direction.NORTH)).setValue(TYPE, ChestType.SINGLE)).setValue(WATERLOGGED, false)); } public static DoubleBlockCombiner.BlockType getBlockType(BlockState state) { ChestType type = state.getValue(TYPE); if (type == ChestType.SINGLE) { return DoubleBlockCombiner.BlockType.SINGLE; } if (type == ChestType.RIGHT) { return DoubleBlockCombiner.BlockType.FIRST; } return DoubleBlockCombiner.BlockType.SECOND; } @Override protected BlockState updateShape(BlockState state, LevelReader level, ScheduledTickAccess ticks, BlockPos pos, Direction directionToNeighbour, BlockPos neighbourPos, BlockState neighbourState, RandomSource random) { if (state.getValue(WATERLOGGED).booleanValue()) { ticks.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level)); } if (this.chestCanConnectTo(neighbourState) && directionToNeighbour.getAxis().isHorizontal()) { ChestType neighbourType = neighbourState.getValue(TYPE); if (state.getValue(TYPE) == ChestType.SINGLE && neighbourType != ChestType.SINGLE && state.getValue(FACING) == neighbourState.getValue(FACING) && ChestBlock.getConnectedDirection(neighbourState) == directionToNeighbour.getOpposite()) { return (BlockState)state.setValue(TYPE, neighbourType.getOpposite()); } } else if (ChestBlock.getConnectedDirection(state) == directionToNeighbour) { return (BlockState)state.setValue(TYPE, ChestType.SINGLE); } return super.updateShape(state, level, ticks, pos, directionToNeighbour, neighbourPos, neighbourState, random); } public boolean chestCanConnectTo(BlockState blockState) { return blockState.is(this); } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return switch (state.getValue(TYPE)) { default -> throw new MatchException(null, null); case ChestType.SINGLE -> SHAPE; case ChestType.LEFT, ChestType.RIGHT -> HALF_SHAPES.get(ChestBlock.getConnectedDirection(state)); }; } public static Direction getConnectedDirection(BlockState state) { Direction facing = state.getValue(FACING); return state.getValue(TYPE) == ChestType.LEFT ? facing.getClockWise() : facing.getCounterClockWise(); } public static BlockPos getConnectedBlockPos(BlockPos pos, BlockState state) { Direction connectedDirection = ChestBlock.getConnectedDirection(state); return pos.relative(connectedDirection); } @Override public BlockState getStateForPlacement(BlockPlaceContext context) { Direction neighbourFacing; ChestType type = ChestType.SINGLE; Direction facingDirection = context.getHorizontalDirection().getOpposite(); FluidState replacedFluidState = context.getLevel().getFluidState(context.getClickedPos()); boolean secondaryUse = context.isSecondaryUseActive(); Direction clickedFace = context.getClickedFace(); if (clickedFace.getAxis().isHorizontal() && secondaryUse && (neighbourFacing = this.candidatePartnerFacing(context.getLevel(), context.getClickedPos(), clickedFace.getOpposite())) != null && neighbourFacing.getAxis() != clickedFace.getAxis()) { facingDirection = neighbourFacing; ChestType chestType = type = facingDirection.getCounterClockWise() == clickedFace.getOpposite() ? ChestType.RIGHT : ChestType.LEFT; } if (type == ChestType.SINGLE && !secondaryUse) { type = this.getChestType(context.getLevel(), context.getClickedPos(), facingDirection); } return (BlockState)((BlockState)((BlockState)this.defaultBlockState().setValue(FACING, facingDirection)).setValue(TYPE, type)).setValue(WATERLOGGED, replacedFluidState.getType() == Fluids.WATER); } protected ChestType getChestType(Level level, BlockPos pos, Direction facingDirection) { if (facingDirection == this.candidatePartnerFacing(level, pos, facingDirection.getClockWise())) { return ChestType.LEFT; } if (facingDirection == this.candidatePartnerFacing(level, pos, facingDirection.getCounterClockWise())) { return ChestType.RIGHT; } return ChestType.SINGLE; } @Override protected FluidState getFluidState(BlockState state) { if (state.getValue(WATERLOGGED).booleanValue()) { return Fluids.WATER.getSource(false); } return super.getFluidState(state); } private @Nullable Direction candidatePartnerFacing(Level level, BlockPos pos, Direction neighbourDirection) { BlockState state = level.getBlockState(pos.relative(neighbourDirection)); return this.chestCanConnectTo(state) && state.getValue(TYPE) == ChestType.SINGLE ? state.getValue(FACING) : null; } @Override protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel level, BlockPos pos, boolean movedByPiston) { Containers.updateNeighboursAfterDestroy(state, level, pos); } @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { if (level instanceof ServerLevel) { ServerLevel serverLevel = (ServerLevel)level; 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(); } public static @Nullable Container getContainer(ChestBlock block, BlockState state, Level level, BlockPos pos, boolean ignoreBeingBlocked) { return block.combine(state, level, pos, ignoreBeingBlocked).apply(CHEST_COMBINER).orElse(null); } @Override public DoubleBlockCombiner.NeighborCombineResult combine(BlockState state, Level level, BlockPos pos, boolean ignoreBeingBlocked) { BiPredicate predicate = ignoreBeingBlocked ? (levelAccessor, blockPos) -> false : ChestBlock::isChestBlockedAt; return DoubleBlockCombiner.combineWithNeigbour((BlockEntityType)this.blockEntityType.get(), ChestBlock::getBlockType, ChestBlock::getConnectedDirection, FACING, state, level, pos, predicate); } @Override protected @Nullable MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { return this.combine(state, level, pos, false).apply(MENU_PROVIDER_COMBINER).orElse(null); } public static DoubleBlockCombiner.Combiner opennessCombiner(final LidBlockEntity entity) { return new DoubleBlockCombiner.Combiner(){ @Override public Float2FloatFunction acceptDouble(ChestBlockEntity first, ChestBlockEntity second) { return partialTickTime -> Math.max(first.getOpenNess(partialTickTime), second.getOpenNess(partialTickTime)); } @Override public Float2FloatFunction acceptSingle(ChestBlockEntity single) { return single::getOpenNess; } @Override public Float2FloatFunction acceptNone() { return entity::getOpenNess; } }; } @Override public BlockEntity newBlockEntity(BlockPos worldPosition, BlockState blockState) { return new ChestBlockEntity(worldPosition, blockState); } @Override public @Nullable BlockEntityTicker getTicker(Level level, BlockState blockState, BlockEntityType type) { return level.isClientSide() ? ChestBlock.createTickerHelper(type, this.blockEntityType(), ChestBlockEntity::lidAnimateTick) : null; } public static boolean isChestBlockedAt(LevelAccessor level, BlockPos pos) { return ChestBlock.isBlockedChestByBlock(level, pos) || ChestBlock.isCatSittingOnChest(level, pos); } private static boolean isBlockedChestByBlock(BlockGetter level, BlockPos pos) { BlockPos above = pos.above(); return level.getBlockState(above).isRedstoneConductor(level, above); } private static boolean isCatSittingOnChest(LevelAccessor level, BlockPos pos) { List cats = level.getEntitiesOfClass(Cat.class, new AABB(pos.getX(), pos.getY() + 1, pos.getZ(), pos.getX() + 1, pos.getY() + 2, pos.getZ() + 1)); if (!cats.isEmpty()) { for (Cat cat : cats) { if (!cat.isInSittingPose()) continue; return true; } } return false; } @Override protected boolean hasAnalogOutputSignal(BlockState state) { return true; } @Override protected int getAnalogOutputSignal(BlockState state, Level level, BlockPos pos, Direction direction) { return AbstractContainerMenu.getRedstoneSignalFromContainer(ChestBlock.getContainer(this, state, level, pos, false)); } @Override protected BlockState rotate(BlockState state, Rotation rotation) { return (BlockState)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(StateDefinition.Builder builder) { builder.add(FACING, TYPE, WATERLOGGED); } @Override protected boolean isPathfindable(BlockState state, PathComputationType type) { 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(); } } public SoundEvent getOpenChestSound() { return this.openSound; } public SoundEvent getCloseChestSound() { return this.closeSound; } }