package net.minecraft.world.level.block; import com.mojang.logging.LogUtils; import com.mojang.serialization.MapCodec; import java.util.Optional; import net.minecraft.BlockUtil; import net.minecraft.BlockUtil.FoundRectangle; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntitySpawnReason; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Relative; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.ScheduledTickAccess; 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.EnumProperty; import net.minecraft.world.level.border.WorldBorder; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.portal.PortalShape; import net.minecraft.world.level.portal.TeleportTransition; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class NetherPortalBlock extends Block implements Portal { public static final MapCodec CODEC = simpleCodec(NetherPortalBlock::new); public static final EnumProperty AXIS = BlockStateProperties.HORIZONTAL_AXIS; private static final Logger LOGGER = LogUtils.getLogger(); protected static final int AABB_OFFSET = 2; protected static final VoxelShape X_AXIS_AABB = Block.box(0.0, 0.0, 6.0, 16.0, 16.0, 10.0); protected static final VoxelShape Z_AXIS_AABB = Block.box(6.0, 0.0, 0.0, 10.0, 16.0, 16.0); @Override public MapCodec codec() { return CODEC; } public NetherPortalBlock(BlockBehaviour.Properties properties) { super(properties); this.registerDefaultState(this.stateDefinition.any().setValue(AXIS, Direction.Axis.X)); } @Override protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { switch ((Direction.Axis)state.getValue(AXIS)) { case Z: return Z_AXIS_AABB; case X: default: return X_AXIS_AABB; } } @Override protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { if (level.dimensionType().natural() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < level.getDifficulty().getId()) { while (level.getBlockState(pos).is(this)) { pos = pos.below(); } if (level.getBlockState(pos).isValidSpawn(level, pos, EntityType.ZOMBIFIED_PIGLIN)) { Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(level, pos.above(), EntitySpawnReason.STRUCTURE); if (entity != null) { entity.setPortalCooldown(); Entity entity2 = entity.getVehicle(); if (entity2 != null) { entity2.setPortalCooldown(); } } } } } @Override protected BlockState updateShape( BlockState blockState, LevelReader levelReader, ScheduledTickAccess scheduledTickAccess, BlockPos blockPos, Direction direction, BlockPos blockPos2, BlockState blockState2, RandomSource randomSource ) { Direction.Axis axis = direction.getAxis(); Direction.Axis axis2 = blockState.getValue(AXIS); boolean bl = axis2 != axis && axis.isHorizontal(); return !bl && !blockState2.is(this) && !PortalShape.findAnyShape(levelReader, blockPos, axis2).isComplete() ? Blocks.AIR.defaultBlockState() : super.updateShape(blockState, levelReader, scheduledTickAccess, blockPos, direction, blockPos2, blockState2, randomSource); } @Override protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { if (entity.canUsePortal(false)) { entity.setAsInsidePortal(this, pos); } } @Override public int getPortalTransitionTime(ServerLevel level, Entity entity) { return entity instanceof Player player ? Math.max( 0, level.getGameRules() .getInt(player.getAbilities().invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY) ) : 0; } @Nullable @Override public TeleportTransition getPortalDestination(ServerLevel serverLevel, Entity entity, BlockPos blockPos) { ResourceKey resourceKey = serverLevel.dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER; ServerLevel serverLevel2 = serverLevel.getServer().getLevel(resourceKey); if (serverLevel2 == null) { return null; } else { boolean bl = serverLevel2.dimension() == Level.NETHER; WorldBorder worldBorder = serverLevel2.getWorldBorder(); double d = DimensionType.getTeleportationScale(serverLevel.dimensionType(), serverLevel2.dimensionType()); BlockPos blockPos2 = worldBorder.clampToBounds(entity.getX() * d, entity.getY(), entity.getZ() * d); return this.getExitPortal(serverLevel2, entity, blockPos, blockPos2, bl, worldBorder); } } @Nullable private TeleportTransition getExitPortal(ServerLevel serverLevel, Entity entity, BlockPos blockPos, BlockPos blockPos2, boolean bl, WorldBorder worldBorder) { Optional optional = serverLevel.getPortalForcer().findClosestPortalPosition(blockPos2, bl, worldBorder); FoundRectangle foundRectangle; TeleportTransition.PostTeleportTransition postTeleportTransition; if (optional.isPresent()) { BlockPos blockPos3 = (BlockPos)optional.get(); BlockState blockState = serverLevel.getBlockState(blockPos3); foundRectangle = BlockUtil.getLargestRectangleAround( blockPos3, blockState.getValue(BlockStateProperties.HORIZONTAL_AXIS), 21, Direction.Axis.Y, 21, blockPosx -> serverLevel.getBlockState(blockPosx) == blockState ); postTeleportTransition = TeleportTransition.PLAY_PORTAL_SOUND.then(entityx -> entityx.placePortalTicket(blockPos3)); } else { Direction.Axis axis = (Direction.Axis)entity.level().getBlockState(blockPos).getOptionalValue(AXIS).orElse(Direction.Axis.X); Optional optional2 = serverLevel.getPortalForcer().createPortal(blockPos2, axis); if (optional2.isEmpty()) { LOGGER.error("Unable to create a portal, likely target out of worldborder"); return null; } foundRectangle = (FoundRectangle)optional2.get(); postTeleportTransition = TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET); } return getDimensionTransitionFromExit(entity, blockPos, foundRectangle, serverLevel, postTeleportTransition); } private static TeleportTransition getDimensionTransitionFromExit( Entity entity, BlockPos blockPos, FoundRectangle foundRectangle, ServerLevel serverLevel, TeleportTransition.PostTeleportTransition postTeleportTransition ) { BlockState blockState = entity.level().getBlockState(blockPos); Direction.Axis axis; Vec3 vec3; if (blockState.hasProperty(BlockStateProperties.HORIZONTAL_AXIS)) { axis = blockState.getValue(BlockStateProperties.HORIZONTAL_AXIS); FoundRectangle foundRectangle2 = BlockUtil.getLargestRectangleAround( blockPos, axis, 21, Direction.Axis.Y, 21, blockPosx -> entity.level().getBlockState(blockPosx) == blockState ); vec3 = entity.getRelativePortalPosition(axis, foundRectangle2); } else { axis = Direction.Axis.X; vec3 = new Vec3(0.5, 0.0, 0.0); } return createDimensionTransition(serverLevel, foundRectangle, axis, vec3, entity, postTeleportTransition); } private static TeleportTransition createDimensionTransition( ServerLevel serverLevel, FoundRectangle foundRectangle, Direction.Axis axis, Vec3 vec3, Entity entity, TeleportTransition.PostTeleportTransition postTeleportTransition ) { BlockPos blockPos = foundRectangle.minCorner; BlockState blockState = serverLevel.getBlockState(blockPos); Direction.Axis axis2 = (Direction.Axis)blockState.getOptionalValue(BlockStateProperties.HORIZONTAL_AXIS).orElse(Direction.Axis.X); double d = foundRectangle.axis1Size; double e = foundRectangle.axis2Size; EntityDimensions entityDimensions = entity.getDimensions(entity.getPose()); int i = axis == axis2 ? 0 : 90; double f = entityDimensions.width() / 2.0 + (d - entityDimensions.width()) * vec3.x(); double g = (e - entityDimensions.height()) * vec3.y(); double h = 0.5 + vec3.z(); boolean bl = axis2 == Direction.Axis.X; Vec3 vec32 = new Vec3(blockPos.getX() + (bl ? f : h), blockPos.getY() + g, blockPos.getZ() + (bl ? h : f)); Vec3 vec33 = PortalShape.findCollisionFreePosition(vec32, serverLevel, entity, entityDimensions); return new TeleportTransition(serverLevel, vec33, Vec3.ZERO, i, 0.0F, Relative.union(Relative.DELTA, Relative.ROTATION), postTeleportTransition); } @Override public Portal.Transition getLocalTransition() { return Portal.Transition.CONFUSION; } @Override public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { if (random.nextInt(100) == 0) { level.playLocalSound( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, SoundEvents.PORTAL_AMBIENT, SoundSource.BLOCKS, 0.5F, random.nextFloat() * 0.4F + 0.8F, false ); } for (int i = 0; i < 4; i++) { double d = pos.getX() + random.nextDouble(); double e = pos.getY() + random.nextDouble(); double f = pos.getZ() + random.nextDouble(); double g = (random.nextFloat() - 0.5) * 0.5; double h = (random.nextFloat() - 0.5) * 0.5; double j = (random.nextFloat() - 0.5) * 0.5; int k = random.nextInt(2) * 2 - 1; if (!level.getBlockState(pos.west()).is(this) && !level.getBlockState(pos.east()).is(this)) { d = pos.getX() + 0.5 + 0.25 * k; g = random.nextFloat() * 2.0F * k; } else { f = pos.getZ() + 0.5 + 0.25 * k; j = random.nextFloat() * 2.0F * k; } level.addParticle(ParticleTypes.PORTAL, d, e, f, g, h, j); } } @Override public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) { return ItemStack.EMPTY; } @Override protected BlockState rotate(BlockState state, Rotation rotation) { switch (rotation) { case COUNTERCLOCKWISE_90: case CLOCKWISE_90: switch ((Direction.Axis)state.getValue(AXIS)) { case Z: return state.setValue(AXIS, Direction.Axis.X); case X: return state.setValue(AXIS, Direction.Axis.Z); default: return state; } default: return state; } } @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(AXIS); } }