package net.minecraft.world.level.block; import com.mojang.serialization.MapCodec; import it.unimi.dsi.fastutil.objects.Object2FloatMap; import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.stats.Stats; import net.minecraft.util.RandomSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.SimpleContainer; import net.minecraft.world.WorldlyContainer; import net.minecraft.world.WorldlyContainerHolder; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; 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.IntegerProperty; 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.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.BooleanOp; 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 ComposterBlock extends Block implements WorldlyContainerHolder { public static final MapCodec CODEC = simpleCodec(ComposterBlock::new); public static final int READY = 8; public static final int MIN_LEVEL = 0; public static final int MAX_LEVEL = 7; public static final IntegerProperty LEVEL = BlockStateProperties.LEVEL_COMPOSTER; public static final Object2FloatMap COMPOSTABLES = new Object2FloatOpenHashMap<>(); private static final int AABB_SIDE_THICKNESS = 2; private static final VoxelShape OUTER_SHAPE = Shapes.block(); private static final VoxelShape[] SHAPES = Util.make(new VoxelShape[9], voxelShapes -> { for (int i = 0; i < 8; i++) { voxelShapes[i] = Shapes.join(OUTER_SHAPE, Block.box(2.0, Math.max(2, 1 + i * 2), 2.0, 14.0, 16.0, 14.0), BooleanOp.ONLY_FIRST); } voxelShapes[8] = voxelShapes[7]; }); @Override public MapCodec codec() { return CODEC; } public static void bootStrap() { COMPOSTABLES.defaultReturnValue(-1.0F); float f = 0.3F; float g = 0.5F; float h = 0.65F; float i = 0.85F; float j = 1.0F; add(0.3F, Items.JUNGLE_LEAVES); add(0.3F, Items.OAK_LEAVES); add(0.3F, Items.SPRUCE_LEAVES); add(0.3F, Items.DARK_OAK_LEAVES); add(0.3F, Items.PALE_OAK_LEAVES); add(0.3F, Items.ACACIA_LEAVES); add(0.3F, Items.CHERRY_LEAVES); add(0.3F, Items.BIRCH_LEAVES); add(0.3F, Items.AZALEA_LEAVES); add(0.3F, Items.MANGROVE_LEAVES); add(0.3F, Items.OAK_SAPLING); add(0.3F, Items.SPRUCE_SAPLING); add(0.3F, Items.BIRCH_SAPLING); add(0.3F, Items.JUNGLE_SAPLING); add(0.3F, Items.ACACIA_SAPLING); add(0.3F, Items.CHERRY_SAPLING); add(0.3F, Items.DARK_OAK_SAPLING); add(0.3F, Items.PALE_OAK_SAPLING); add(0.3F, Items.MANGROVE_PROPAGULE); add(0.3F, Items.BEETROOT_SEEDS); add(0.3F, Items.DRIED_KELP); add(0.3F, Items.SHORT_GRASS); add(0.3F, Items.KELP); add(0.3F, Items.MELON_SEEDS); add(0.3F, Items.PUMPKIN_SEEDS); add(0.3F, Items.SEAGRASS); add(0.3F, Items.SWEET_BERRIES); add(0.3F, Items.GLOW_BERRIES); add(0.3F, Items.WHEAT_SEEDS); add(0.3F, Items.MOSS_CARPET); add(0.3F, Items.PALE_MOSS_CARPET); add(0.3F, Items.PALE_HANGING_MOSS); add(0.3F, Items.PINK_PETALS); add(0.3F, Items.SMALL_DRIPLEAF); add(0.3F, Items.HANGING_ROOTS); add(0.3F, Items.MANGROVE_ROOTS); add(0.3F, Items.TORCHFLOWER_SEEDS); add(0.3F, Items.PITCHER_POD); add(0.5F, Items.DRIED_KELP_BLOCK); add(0.5F, Items.TALL_GRASS); add(0.5F, Items.FLOWERING_AZALEA_LEAVES); add(0.5F, Items.CACTUS); add(0.5F, Items.SUGAR_CANE); add(0.5F, Items.VINE); add(0.5F, Items.NETHER_SPROUTS); add(0.5F, Items.WEEPING_VINES); add(0.5F, Items.TWISTING_VINES); add(0.5F, Items.MELON_SLICE); add(0.5F, Items.GLOW_LICHEN); add(0.65F, Items.SEA_PICKLE); add(0.65F, Items.LILY_PAD); add(0.65F, Items.PUMPKIN); add(0.65F, Items.CARVED_PUMPKIN); add(0.65F, Items.MELON); add(0.65F, Items.APPLE); add(0.65F, Items.BEETROOT); add(0.65F, Items.CARROT); add(0.65F, Items.COCOA_BEANS); add(0.65F, Items.POTATO); add(0.65F, Items.WHEAT); add(0.65F, Items.BROWN_MUSHROOM); add(0.65F, Items.RED_MUSHROOM); add(0.65F, Items.MUSHROOM_STEM); add(0.65F, Items.CRIMSON_FUNGUS); add(0.65F, Items.WARPED_FUNGUS); add(0.65F, Items.NETHER_WART); add(0.65F, Items.CRIMSON_ROOTS); add(0.65F, Items.WARPED_ROOTS); add(0.65F, Items.SHROOMLIGHT); add(0.65F, Items.DANDELION); add(0.65F, Items.POPPY); add(0.65F, Items.BLUE_ORCHID); add(0.65F, Items.ALLIUM); add(0.65F, Items.AZURE_BLUET); add(0.65F, Items.RED_TULIP); add(0.65F, Items.ORANGE_TULIP); add(0.65F, Items.WHITE_TULIP); add(0.65F, Items.PINK_TULIP); add(0.65F, Items.OXEYE_DAISY); add(0.65F, Items.CORNFLOWER); add(0.65F, Items.LILY_OF_THE_VALLEY); add(0.65F, Items.WITHER_ROSE); add(0.65F, Items.FERN); add(0.65F, Items.SUNFLOWER); add(0.65F, Items.LILAC); add(0.65F, Items.ROSE_BUSH); add(0.65F, Items.PEONY); add(0.65F, Items.LARGE_FERN); add(0.65F, Items.SPORE_BLOSSOM); add(0.65F, Items.AZALEA); add(0.65F, Items.MOSS_BLOCK); add(0.65F, Items.PALE_MOSS_BLOCK); add(0.65F, Items.BIG_DRIPLEAF); add(0.85F, Items.HAY_BLOCK); add(0.85F, Items.BROWN_MUSHROOM_BLOCK); add(0.85F, Items.RED_MUSHROOM_BLOCK); add(0.85F, Items.NETHER_WART_BLOCK); add(0.85F, Items.WARPED_WART_BLOCK); add(0.85F, Items.FLOWERING_AZALEA); add(0.85F, Items.BREAD); add(0.85F, Items.BAKED_POTATO); add(0.85F, Items.COOKIE); add(0.85F, Items.TORCHFLOWER); add(0.85F, Items.PITCHER_PLANT); add(1.0F, Items.CAKE); add(1.0F, Items.PUMPKIN_PIE); } private static void add(float chance, ItemLike item) { COMPOSTABLES.put(item.asItem(), chance); } public ComposterBlock(BlockBehaviour.Properties properties) { super(properties); this.registerDefaultState(this.stateDefinition.any().setValue(LEVEL, 0)); } public static void handleFill(Level level, BlockPos pos, boolean success) { BlockState blockState = level.getBlockState(pos); level.playLocalSound(pos, success ? SoundEvents.COMPOSTER_FILL_SUCCESS : SoundEvents.COMPOSTER_FILL, SoundSource.BLOCKS, 1.0F, 1.0F, false); double d = blockState.getShape(level, pos).max(Direction.Axis.Y, 0.5, 0.5) + 0.03125; double e = 0.13125F; double f = 0.7375F; RandomSource randomSource = level.getRandom(); for (int i = 0; i < 10; i++) { double g = randomSource.nextGaussian() * 0.02; double h = randomSource.nextGaussian() * 0.02; double j = randomSource.nextGaussian() * 0.02; level.addParticle( ParticleTypes.COMPOSTER, pos.getX() + 0.13125F + 0.7375F * randomSource.nextFloat(), pos.getY() + d + randomSource.nextFloat() * (1.0 - d), pos.getZ() + 0.13125F + 0.7375F * randomSource.nextFloat(), g, h, j ); } } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return SHAPES[state.getValue(LEVEL)]; } @Override protected VoxelShape getInteractionShape(BlockState state, BlockGetter level, BlockPos pos) { return OUTER_SHAPE; } @Override protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return SHAPES[0]; } @Override protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) { if ((Integer)state.getValue(LEVEL) == 7) { level.scheduleTick(pos, state.getBlock(), 20); } } @Override protected InteractionResult useItemOn( ItemStack itemStack, BlockState blockState, Level level, BlockPos blockPos, Player player, InteractionHand interactionHand, BlockHitResult blockHitResult ) { int i = (Integer)blockState.getValue(LEVEL); if (i < 8 && COMPOSTABLES.containsKey(itemStack.getItem())) { if (i < 7 && !level.isClientSide) { BlockState blockState2 = addItem(player, blockState, level, blockPos, itemStack); level.levelEvent(1500, blockPos, blockState != blockState2 ? 1 : 0); player.awardStat(Stats.ITEM_USED.get(itemStack.getItem())); itemStack.consume(1, player); } return InteractionResult.SUCCESS; } else { return super.useItemOn(itemStack, blockState, level, blockPos, player, interactionHand, blockHitResult); } } @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) { int i = (Integer)state.getValue(LEVEL); if (i == 8) { extractProduce(player, state, level, pos); return InteractionResult.SUCCESS; } else { return InteractionResult.PASS; } } public static BlockState insertItem(Entity entity, BlockState state, ServerLevel level, ItemStack stack, BlockPos pos) { int i = (Integer)state.getValue(LEVEL); if (i < 7 && COMPOSTABLES.containsKey(stack.getItem())) { BlockState blockState = addItem(entity, state, level, pos, stack); stack.shrink(1); return blockState; } else { return state; } } public static BlockState extractProduce(Entity entity, BlockState state, Level level, BlockPos pos) { if (!level.isClientSide) { Vec3 vec3 = Vec3.atLowerCornerWithOffset(pos, 0.5, 1.01, 0.5).offsetRandom(level.random, 0.7F); ItemEntity itemEntity = new ItemEntity(level, vec3.x(), vec3.y(), vec3.z(), new ItemStack(Items.BONE_MEAL)); itemEntity.setDefaultPickUpDelay(); level.addFreshEntity(itemEntity); } BlockState blockState = empty(entity, state, level, pos); level.playSound(null, pos, SoundEvents.COMPOSTER_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F); return blockState; } static BlockState empty(@Nullable Entity entity, BlockState state, LevelAccessor level, BlockPos pos) { BlockState blockState = state.setValue(LEVEL, 0); level.setBlock(pos, blockState, 3); level.gameEvent(GameEvent.BLOCK_CHANGE, pos, Context.of(entity, blockState)); return blockState; } static BlockState addItem(@Nullable Entity entity, BlockState state, LevelAccessor level, BlockPos pos, ItemStack stack) { int i = (Integer)state.getValue(LEVEL); float f = COMPOSTABLES.getFloat(stack.getItem()); if ((i != 0 || !(f > 0.0F)) && !(level.getRandom().nextDouble() < f)) { return state; } else { int j = i + 1; BlockState blockState = state.setValue(LEVEL, j); level.setBlock(pos, blockState, 3); level.gameEvent(GameEvent.BLOCK_CHANGE, pos, Context.of(entity, blockState)); if (j == 7) { level.scheduleTick(pos, state.getBlock(), 20); } return blockState; } } @Override protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { if ((Integer)state.getValue(LEVEL) == 7) { level.setBlock(pos, state.cycle(LEVEL), 3); level.playSound(null, pos, SoundEvents.COMPOSTER_READY, SoundSource.BLOCKS, 1.0F, 1.0F); } } @Override protected boolean hasAnalogOutputSignal(BlockState state) { return true; } @Override protected int getAnalogOutputSignal(BlockState state, Level level, BlockPos pos) { return (Integer)state.getValue(LEVEL); } @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(LEVEL); } @Override protected boolean isPathfindable(BlockState state, PathComputationType pathComputationType) { return false; } @Override public WorldlyContainer getContainer(BlockState state, LevelAccessor level, BlockPos pos) { int i = (Integer)state.getValue(LEVEL); if (i == 8) { return new ComposterBlock.OutputContainer(state, level, pos, new ItemStack(Items.BONE_MEAL)); } else { return (WorldlyContainer)(i < 7 ? new ComposterBlock.InputContainer(state, level, pos) : new ComposterBlock.EmptyContainer()); } } static class EmptyContainer extends SimpleContainer implements WorldlyContainer { public EmptyContainer() { super(0); } @Override public int[] getSlotsForFace(Direction side) { return new int[0]; } @Override public boolean canPlaceItemThroughFace(int index, ItemStack itemStack, @Nullable Direction direction) { return false; } @Override public boolean canTakeItemThroughFace(int index, ItemStack stack, Direction direction) { return false; } } static class InputContainer extends SimpleContainer implements WorldlyContainer { private final BlockState state; private final LevelAccessor level; private final BlockPos pos; private boolean changed; public InputContainer(BlockState state, LevelAccessor level, BlockPos pos) { super(1); this.state = state; this.level = level; this.pos = pos; } @Override public int getMaxStackSize() { return 1; } @Override public int[] getSlotsForFace(Direction side) { return side == Direction.UP ? new int[]{0} : new int[0]; } @Override public boolean canPlaceItemThroughFace(int index, ItemStack itemStack, @Nullable Direction direction) { return !this.changed && direction == Direction.UP && ComposterBlock.COMPOSTABLES.containsKey(itemStack.getItem()); } @Override public boolean canTakeItemThroughFace(int index, ItemStack stack, Direction direction) { return false; } @Override public void setChanged() { ItemStack itemStack = this.getItem(0); if (!itemStack.isEmpty()) { this.changed = true; BlockState blockState = ComposterBlock.addItem(null, this.state, this.level, this.pos, itemStack); this.level.levelEvent(1500, this.pos, blockState != this.state ? 1 : 0); this.removeItemNoUpdate(0); } } } static class OutputContainer extends SimpleContainer implements WorldlyContainer { private final BlockState state; private final LevelAccessor level; private final BlockPos pos; private boolean changed; public OutputContainer(BlockState state, LevelAccessor level, BlockPos pos, ItemStack stack) { super(stack); this.state = state; this.level = level; this.pos = pos; } @Override public int getMaxStackSize() { return 1; } @Override public int[] getSlotsForFace(Direction side) { return side == Direction.DOWN ? new int[]{0} : new int[0]; } @Override public boolean canPlaceItemThroughFace(int index, ItemStack itemStack, @Nullable Direction direction) { return false; } @Override public boolean canTakeItemThroughFace(int index, ItemStack stack, Direction direction) { return !this.changed && direction == Direction.DOWN && stack.is(Items.BONE_MEAL); } @Override public void setChanged() { ComposterBlock.empty(null, this.state, this.level, this.pos); this.changed = true; } } }