package net.minecraft.world.level.block; import com.google.common.collect.ImmutableList; import com.mojang.serialization.MapCodec; import java.util.Objects; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Vec3i; import net.minecraft.core.BlockPos.MutableBlockPos; import net.minecraft.core.Direction.Plane; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.FluidTags; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.attribute.EnvironmentAttributes; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.vehicle.DismountHelper; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.CollisionGetter; import net.minecraft.world.level.Explosion; import net.minecraft.world.level.ExplosionDamageCalculator; import net.minecraft.world.level.Level; 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.level.material.FluidState; import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.level.storage.LevelData.RespawnData; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; public class RespawnAnchorBlock extends Block { public static final MapCodec CODEC = simpleCodec(RespawnAnchorBlock::new); public static final int MIN_CHARGES = 0; public static final int MAX_CHARGES = 4; public static final IntegerProperty CHARGE = BlockStateProperties.RESPAWN_ANCHOR_CHARGES; private static final ImmutableList RESPAWN_HORIZONTAL_OFFSETS = ImmutableList.of( new Vec3i(0, 0, -1), new Vec3i(-1, 0, 0), new Vec3i(0, 0, 1), new Vec3i(1, 0, 0), new Vec3i(-1, 0, -1), new Vec3i(1, 0, -1), new Vec3i(-1, 0, 1), new Vec3i(1, 0, 1) ); private static final ImmutableList RESPAWN_OFFSETS = new com.google.common.collect.ImmutableList.Builder() .addAll(RESPAWN_HORIZONTAL_OFFSETS) .addAll(RESPAWN_HORIZONTAL_OFFSETS.stream().map(Vec3i::below).iterator()) .addAll(RESPAWN_HORIZONTAL_OFFSETS.stream().map(Vec3i::above).iterator()) .add(new Vec3i(0, 1, 0)) .build(); @Override public MapCodec codec() { return CODEC; } public RespawnAnchorBlock(final BlockBehaviour.Properties properties) { super(properties); this.registerDefaultState(this.stateDefinition.any().setValue(CHARGE, 0)); } @Override protected InteractionResult useItemOn( final ItemStack itemStack, final BlockState state, final Level level, final BlockPos pos, final Player player, final InteractionHand hand, final BlockHitResult hitResult ) { if (isRespawnFuel(itemStack) && canBeCharged(state)) { charge(player, level, pos, state); itemStack.consume(1, player); return InteractionResult.SUCCESS; } else { return (InteractionResult)(hand == InteractionHand.MAIN_HAND && isRespawnFuel(player.getItemInHand(InteractionHand.OFF_HAND)) && canBeCharged(state) ? InteractionResult.PASS : InteractionResult.TRY_WITH_EMPTY_HAND); } } @Override protected InteractionResult useWithoutItem(final BlockState state, final Level level, final BlockPos pos, final Player player, final BlockHitResult hitResult) { if ((Integer)state.getValue(CHARGE) == 0) { return InteractionResult.PASS; } else if (level instanceof ServerLevel serverLevel) { if (!canSetSpawn(serverLevel, pos)) { this.explode(state, serverLevel, pos); return InteractionResult.SUCCESS_SERVER; } else { if (player instanceof ServerPlayer serverPlayer) { ServerPlayer.RespawnConfig respawnConfig = serverPlayer.getRespawnConfig(); ServerPlayer.RespawnConfig newRespawnConfig = new ServerPlayer.RespawnConfig(RespawnData.of(serverLevel.dimension(), pos, 0.0F, 0.0F), false); if (respawnConfig == null || !respawnConfig.isSamePosition(newRespawnConfig)) { serverPlayer.setRespawnPosition(newRespawnConfig, true); serverLevel.playSound(null, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F); return InteractionResult.SUCCESS_SERVER; } } return InteractionResult.CONSUME; } } else { return InteractionResult.CONSUME; } } private static boolean isRespawnFuel(final ItemStack itemInHand) { return itemInHand.is(Items.GLOWSTONE); } private static boolean canBeCharged(final BlockState state) { return (Integer)state.getValue(CHARGE) < 4; } private static boolean isWaterThatWouldFlow(final BlockPos pos, final Level level) { FluidState fluid = level.getFluidState(pos); if (!fluid.is(FluidTags.WATER)) { return false; } else if (fluid.isSource()) { return true; } else { float amount = fluid.getAmount(); if (amount < 2.0F) { return false; } else { FluidState fluidBelow = level.getFluidState(pos.below()); return !fluidBelow.is(FluidTags.WATER); } } } private void explode(final BlockState state, final ServerLevel level, final BlockPos pos) { level.removeBlock(pos, false); boolean anyWaterNeighbors = Plane.HORIZONTAL.stream().map(pos::relative).anyMatch(neighborPos -> isWaterThatWouldFlow(neighborPos, level)); final boolean inWater = anyWaterNeighbors || level.getFluidState(pos.above()).is(FluidTags.WATER); ExplosionDamageCalculator damageCalculator = new ExplosionDamageCalculator() { { Objects.requireNonNull(RespawnAnchorBlock.this); } @Override public Optional getBlockExplosionResistance( final Explosion explosion, final BlockGetter levelx, final BlockPos testPos, final BlockState block, final FluidState fluid ) { return testPos.equals(pos) && inWater ? Optional.of(Blocks.WATER.getExplosionResistance()) : super.getBlockExplosionResistance(explosion, levelx, testPos, block, fluid); } }; Vec3 boomPos = Vec3.atCenterOf(pos); level.explode(null, level.damageSources().badRespawnPointExplosion(boomPos), damageCalculator, boomPos, 5.0F, true, Level.ExplosionInteraction.BLOCK); } public static boolean canSetSpawn(final ServerLevel level, final BlockPos pos) { return level.environmentAttributes().getValue(EnvironmentAttributes.RESPAWN_ANCHOR_WORKS, pos); } public static void charge(@Nullable final Entity sourceEntity, final Level level, final BlockPos pos, final BlockState state) { BlockState newState = state.setValue(CHARGE, (Integer)state.getValue(CHARGE) + 1); level.setBlock(pos, newState, 3); level.gameEvent(GameEvent.BLOCK_CHANGE, pos, Context.of(sourceEntity, newState)); level.playSound(null, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, SoundEvents.RESPAWN_ANCHOR_CHARGE, SoundSource.BLOCKS, 1.0F, 1.0F); } @Override public void animateTick(final BlockState state, final Level level, final BlockPos pos, final RandomSource random) { if ((Integer)state.getValue(CHARGE) != 0) { if (random.nextInt(100) == 0) { level.playLocalSound(pos, SoundEvents.RESPAWN_ANCHOR_AMBIENT, SoundSource.BLOCKS, 1.0F, 1.0F, false); } double x = pos.getX() + 0.5 + (0.5 - random.nextDouble()); double y = pos.getY() + 1.0; double z = pos.getZ() + 0.5 + (0.5 - random.nextDouble()); double ya = random.nextFloat() * 0.04; level.addParticle(ParticleTypes.REVERSE_PORTAL, x, y, z, 0.0, ya, 0.0); } } @Override protected void createBlockStateDefinition(final Builder builder) { builder.add(CHARGE); } @Override protected boolean hasAnalogOutputSignal(final BlockState state) { return true; } public static int getScaledChargeLevel(final BlockState state, final int maximum) { return Mth.floor(((Integer)state.getValue(CHARGE) - 0) / 4.0F * maximum); } @Override protected int getAnalogOutputSignal(final BlockState state, final Level level, final BlockPos pos, final Direction direction) { return getScaledChargeLevel(state, 15); } public static Optional findStandUpPosition(final EntityType type, final CollisionGetter level, final BlockPos pos) { Optional safePosition = findStandUpPosition(type, level, pos, true); return safePosition.isPresent() ? safePosition : findStandUpPosition(type, level, pos, false); } private static Optional findStandUpPosition(final EntityType type, final CollisionGetter level, final BlockPos pos, final boolean checkDangerous) { MutableBlockPos blockPos = new MutableBlockPos(); for (Vec3i offset : RESPAWN_OFFSETS) { blockPos.set(pos).move(offset); Vec3 position = DismountHelper.findSafeDismountLocation(type, level, blockPos, checkDangerous); if (position != null) { return Optional.of(position); } } return Optional.empty(); } @Override protected boolean isPathfindable(final BlockState state, final PathComputationType type) { return false; } }