392 lines
18 KiB
Java
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;
|
|
}
|
|
}
|
|
|