281 lines
11 KiB
Java
281 lines
11 KiB
Java
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<NetherPortalBlock> CODEC = simpleCodec(NetherPortalBlock::new);
|
|
public static final EnumProperty<Direction.Axis> 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<NetherPortalBlock> 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<Level> 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<BlockPos> 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<FoundRectangle> 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<Block, BlockState> builder) {
|
|
builder.add(AXIS);
|
|
}
|
|
}
|