package net.minecraft.world.level.block; import com.mojang.serialization.MapCodec; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.BlockTags; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ambient.Bat; import net.minecraft.world.entity.animal.Turtle; import net.minecraft.world.entity.monster.Zombie; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition.Builder; 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.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; public class TurtleEggBlock extends Block { public static final MapCodec CODEC = simpleCodec(TurtleEggBlock::new); public static final int MAX_HATCH_LEVEL = 2; public static final int MIN_EGGS = 1; public static final int MAX_EGGS = 4; private static final VoxelShape ONE_EGG_AABB = Block.box(3.0, 0.0, 3.0, 12.0, 7.0, 12.0); private static final VoxelShape MULTIPLE_EGGS_AABB = Block.box(1.0, 0.0, 1.0, 15.0, 7.0, 15.0); public static final IntegerProperty HATCH = BlockStateProperties.HATCH; public static final IntegerProperty EGGS = BlockStateProperties.EGGS; @Override public MapCodec codec() { return CODEC; } public TurtleEggBlock(BlockBehaviour.Properties properties) { super(properties); this.registerDefaultState(this.stateDefinition.any().setValue(HATCH, 0).setValue(EGGS, 1)); } @Override public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) { if (!entity.isSteppingCarefully()) { this.destroyEgg(level, state, pos, entity, 100); } super.stepOn(level, pos, state, entity); } @Override public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) { if (!(entity instanceof Zombie)) { this.destroyEgg(level, state, pos, entity, 3); } super.fallOn(level, state, pos, entity, fallDistance); } private void destroyEgg(Level level, BlockState state, BlockPos pos, Entity entity, int chance) { if (state.is(Blocks.TURTLE_EGG) && level instanceof ServerLevel serverLevel && this.canDestroyEgg(serverLevel, entity) && level.random.nextInt(chance) == 0) { this.decreaseEggs(serverLevel, pos, state); } } private void decreaseEggs(Level level, BlockPos pos, BlockState state) { level.playSound(null, pos, SoundEvents.TURTLE_EGG_BREAK, SoundSource.BLOCKS, 0.7F, 0.9F + level.random.nextFloat() * 0.2F); int i = (Integer)state.getValue(EGGS); if (i <= 1) { level.destroyBlock(pos, false); } else { level.setBlock(pos, state.setValue(EGGS, i - 1), 2); level.gameEvent(GameEvent.BLOCK_DESTROY, pos, Context.of(state)); level.levelEvent(2001, pos, Block.getId(state)); } } @Override protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { if (this.shouldUpdateHatchLevel(level) && onSand(level, pos)) { int i = (Integer)state.getValue(HATCH); if (i < 2) { level.playSound(null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); level.setBlock(pos, state.setValue(HATCH, i + 1), 2); level.gameEvent(GameEvent.BLOCK_CHANGE, pos, Context.of(state)); } else { level.playSound(null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F); level.removeBlock(pos, false); level.gameEvent(GameEvent.BLOCK_DESTROY, pos, Context.of(state)); for (int j = 0; j < state.getValue(EGGS); j++) { level.levelEvent(2001, pos, Block.getId(state)); Turtle turtle = EntityType.TURTLE.create(level, EntitySpawnReason.BREEDING); if (turtle != null) { turtle.setAge(-24000); turtle.setHomePos(pos); turtle.moveTo(pos.getX() + 0.3 + j * 0.2, pos.getY(), pos.getZ() + 0.3, 0.0F, 0.0F); level.addFreshEntity(turtle); } } } } } public static boolean onSand(BlockGetter level, BlockPos pos) { return isSand(level, pos.below()); } public static boolean isSand(BlockGetter reader, BlockPos pos) { return reader.getBlockState(pos).is(BlockTags.SAND); } @Override protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) { if (onSand(level, pos) && !level.isClientSide) { level.levelEvent(2012, pos, 15); } } private boolean shouldUpdateHatchLevel(Level level) { float f = level.getTimeOfDay(1.0F); return f < 0.69 && f > 0.65 ? true : level.random.nextInt(500) == 0; } @Override public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) { super.playerDestroy(level, player, pos, state, blockEntity, tool); this.decreaseEggs(level, pos, state); } @Override protected boolean canBeReplaced(BlockState state, BlockPlaceContext useContext) { return !useContext.isSecondaryUseActive() && useContext.getItemInHand().is(this.asItem()) && state.getValue(EGGS) < 4 ? true : super.canBeReplaced(state, useContext); } @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext context) { BlockState blockState = context.getLevel().getBlockState(context.getClickedPos()); return blockState.is(this) ? blockState.setValue(EGGS, Math.min(4, (Integer)blockState.getValue(EGGS) + 1)) : super.getStateForPlacement(context); } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { return state.getValue(EGGS) > 1 ? MULTIPLE_EGGS_AABB : ONE_EGG_AABB; } @Override protected void createBlockStateDefinition(Builder builder) { builder.add(HATCH, EGGS); } private boolean canDestroyEgg(ServerLevel level, Entity entity) { if (entity instanceof Turtle || entity instanceof Bat) { return false; } else { return !(entity instanceof LivingEntity) ? false : entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); } } }