package net.minecraft.world.level.block; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.BlockTags; import net.minecraft.util.RandomSource; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; public class MushroomBlock extends VegetationBlock implements BonemealableBlock { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( ResourceKey.codec(Registries.CONFIGURED_FEATURE).fieldOf("feature").forGetter(mushroomBlock -> mushroomBlock.feature), propertiesCodec() ) .apply(instance, MushroomBlock::new) ); private static final VoxelShape SHAPE = Block.column(6.0, 0.0, 6.0); private final ResourceKey> feature; @Override public MapCodec codec() { return CODEC; } public MushroomBlock(ResourceKey> feature, BlockBehaviour.Properties properties) { super(properties); this.feature = feature; } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return SHAPE; } @Override protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { if (random.nextInt(25) == 0) { int i = 5; int j = 4; for (BlockPos blockPos : BlockPos.betweenClosed(pos.offset(-4, -1, -4), pos.offset(4, 1, 4))) { if (level.getBlockState(blockPos).is(this)) { if (--i <= 0) { return; } } } BlockPos blockPos2 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1); for (int k = 0; k < 4; k++) { if (level.isEmptyBlock(blockPos2) && state.canSurvive(level, blockPos2)) { pos = blockPos2; } blockPos2 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1); } if (level.isEmptyBlock(blockPos2) && state.canSurvive(level, blockPos2)) { level.setBlock(blockPos2, state, 2); } } } @Override protected boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos) { return state.isSolidRender(); } @Override protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { BlockPos blockPos = pos.below(); BlockState blockState = level.getBlockState(blockPos); return blockState.is(BlockTags.MUSHROOM_GROW_BLOCK) ? true : level.getRawBrightness(pos, 0) < 13 && this.mayPlaceOn(blockState, level, blockPos); } public boolean growMushroom(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { Optional>> optional = level.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(this.feature); if (optional.isEmpty()) { return false; } else { level.removeBlock(pos, false); if (((ConfiguredFeature)((Holder)optional.get()).value()).place(level, level.getChunkSource().getGenerator(), random, pos)) { return true; } else { level.setBlock(pos, state, 3); return false; } } } @Override public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) { return true; } @Override public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) { return random.nextFloat() < 0.4; } @Override public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { this.growMushroom(level, pos, state, random); } }