2025-11-24 22:52:51 +03:00

392 lines
18 KiB
Java

/*
* 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<ChestBlockEntity>
implements SimpleWaterloggedBlock {
public static final MapCodec<ChestBlock> 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<Direction> FACING = HorizontalDirectionalBlock.FACING;
public static final EnumProperty<ChestType> 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<Direction, VoxelShape> 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<ChestBlockEntity, Optional<Container>> CHEST_COMBINER = new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<Container>>(){
@Override
public Optional<Container> acceptDouble(ChestBlockEntity first, ChestBlockEntity second) {
return Optional.of(new CompoundContainer(first, second));
}
@Override
public Optional<Container> acceptSingle(ChestBlockEntity single) {
return Optional.of(single);
}
@Override
public Optional<Container> acceptNone() {
return Optional.empty();
}
};
private static final DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>> MENU_PROVIDER_COMBINER = new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>(){
@Override
public Optional<MenuProvider> 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<MenuProvider> acceptSingle(ChestBlockEntity single) {
return Optional.of(single);
}
@Override
public Optional<MenuProvider> acceptNone() {
return Optional.empty();
}
};
@Override
public MapCodec<? extends ChestBlock> codec() {
return CODEC;
}
protected ChestBlock(Supplier<BlockEntityType<? extends ChestBlockEntity>> 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<Identifier> getOpenChestStat() {
return Stats.CUSTOM.get(Stats.OPEN_CHEST);
}
public BlockEntityType<? extends ChestBlockEntity> 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<? extends ChestBlockEntity> combine(BlockState state, Level level, BlockPos pos, boolean ignoreBeingBlocked) {
BiPredicate<LevelAccessor, BlockPos> 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<ChestBlockEntity, Float2FloatFunction> opennessCombiner(final LidBlockEntity entity) {
return new DoubleBlockCombiner.Combiner<ChestBlockEntity, Float2FloatFunction>(){
@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 <T extends BlockEntity> @Nullable BlockEntityTicker<T> getTicker(Level level, BlockState blockState, BlockEntityType<T> 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<Cat> 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<Block, BlockState> 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;
}
}