package net.minecraft.world.level.block.entity; import java.util.List; import java.util.stream.IntStream; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; import net.minecraft.core.NonNullList; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; import net.minecraft.world.ContainerHelper; import net.minecraft.world.WorldlyContainer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.monster.Shulker; 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.ShulkerBoxMenu; import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.ShulkerBoxBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer { public static final int COLUMNS = 9; public static final int ROWS = 3; public static final int CONTAINER_SIZE = 27; public static final int EVENT_SET_OPEN_COUNT = 1; public static final int OPENING_TICK_LENGTH = 10; public static final float MAX_LID_HEIGHT = 0.5F; public static final float MAX_LID_ROTATION = 270.0F; private static final int[] SLOTS = IntStream.range(0, 27).toArray(); private NonNullList itemStacks = NonNullList.withSize(27, ItemStack.EMPTY); private int openCount; private ShulkerBoxBlockEntity.AnimationStatus animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED; private float progress; private float progressOld; @Nullable private final DyeColor color; public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, BlockState blockState) { super(BlockEntityType.SHULKER_BOX, pos, blockState); this.color = color; } public ShulkerBoxBlockEntity(BlockPos pos, BlockState blockState) { super(BlockEntityType.SHULKER_BOX, pos, blockState); this.color = ShulkerBoxBlock.getColorFromBlock(blockState.getBlock()); } public static void tick(Level level, BlockPos pos, BlockState state, ShulkerBoxBlockEntity blockEntity) { blockEntity.updateAnimation(level, pos, state); } private void updateAnimation(Level level, BlockPos pos, BlockState state) { this.progressOld = this.progress; switch (this.animationStatus) { case CLOSED: this.progress = 0.0F; break; case OPENING: this.progress += 0.1F; if (this.progressOld == 0.0F) { doNeighborUpdates(level, pos, state); } if (this.progress >= 1.0F) { this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.OPENED; this.progress = 1.0F; doNeighborUpdates(level, pos, state); } this.moveCollidedEntities(level, pos, state); break; case OPENED: this.progress = 1.0F; break; case CLOSING: this.progress -= 0.1F; if (this.progressOld == 1.0F) { doNeighborUpdates(level, pos, state); } if (this.progress <= 0.0F) { this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED; this.progress = 0.0F; doNeighborUpdates(level, pos, state); } } } public ShulkerBoxBlockEntity.AnimationStatus getAnimationStatus() { return this.animationStatus; } public AABB getBoundingBox(BlockState state) { return Shulker.getProgressAabb(1.0F, state.getValue(ShulkerBoxBlock.FACING), 0.5F * this.getProgress(1.0F)); } private void moveCollidedEntities(Level level, BlockPos pos, BlockState state) { if (state.getBlock() instanceof ShulkerBoxBlock) { Direction direction = state.getValue(ShulkerBoxBlock.FACING); AABB aABB = Shulker.getProgressDeltaAabb(1.0F, direction, this.progressOld, this.progress).move(pos); List list = level.getEntities(null, aABB); if (!list.isEmpty()) { for (Entity entity : list) { if (entity.getPistonPushReaction() != PushReaction.IGNORE) { entity.move( MoverType.SHULKER_BOX, new Vec3( (aABB.getXsize() + 0.01) * direction.getStepX(), (aABB.getYsize() + 0.01) * direction.getStepY(), (aABB.getZsize() + 0.01) * direction.getStepZ() ) ); } } } } } @Override public int getContainerSize() { return this.itemStacks.size(); } @Override public boolean triggerEvent(int id, int type) { if (id == 1) { this.openCount = type; if (type == 0) { this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSING; } if (type == 1) { this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.OPENING; } return true; } else { return super.triggerEvent(id, type); } } private static void doNeighborUpdates(Level level, BlockPos pos, BlockState state) { state.updateNeighbourShapes(level, pos, 3); level.updateNeighborsAt(pos, state.getBlock()); } @Override public void startOpen(Player player) { if (!this.remove && !player.isSpectator()) { if (this.openCount < 0) { this.openCount = 0; } this.openCount++; this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); if (this.openCount == 1) { this.level.gameEvent(player, GameEvent.CONTAINER_OPEN, this.worldPosition); this.level.playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); } } } @Override public void stopOpen(Player player) { if (!this.remove && !player.isSpectator()) { this.openCount--; this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount); if (this.openCount <= 0) { this.level.gameEvent(player, GameEvent.CONTAINER_CLOSE, this.worldPosition); this.level.playSound(null, this.worldPosition, SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F); } } } @Override protected Component getDefaultName() { return Component.translatable("container.shulkerBox"); } @Override protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) { super.loadAdditional(tag, registries); this.loadFromTag(tag, registries); } @Override protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) { super.saveAdditional(tag, registries); if (!this.trySaveLootTable(tag)) { ContainerHelper.saveAllItems(tag, this.itemStacks, false, registries); } } public void loadFromTag(CompoundTag tag, HolderLookup.Provider levelRegistry) { this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); if (!this.tryLoadLootTable(tag) && tag.contains("Items", 9)) { ContainerHelper.loadAllItems(tag, this.itemStacks, levelRegistry); } } @Override protected NonNullList getItems() { return this.itemStacks; } @Override protected void setItems(NonNullList items) { this.itemStacks = items; } @Override public int[] getSlotsForFace(Direction side) { return SLOTS; } @Override public boolean canPlaceItemThroughFace(int index, ItemStack itemStack, @Nullable Direction direction) { return !(Block.byItem(itemStack.getItem()) instanceof ShulkerBoxBlock); } @Override public boolean canTakeItemThroughFace(int index, ItemStack stack, Direction direction) { return true; } public float getProgress(float partialTicks) { return Mth.lerp(partialTicks, this.progressOld, this.progress); } @Nullable public DyeColor getColor() { return this.color; } @Override protected AbstractContainerMenu createMenu(int containerId, Inventory inventory) { return new ShulkerBoxMenu(containerId, inventory, this); } public boolean isClosed() { return this.animationStatus == ShulkerBoxBlockEntity.AnimationStatus.CLOSED; } public static enum AnimationStatus { CLOSED, OPENING, OPENED, CLOSING; } }