diff --git a/src/main/java/com/mrbysco/telepastries/blocks/cake/BlockCakeBase.java b/src/main/java/com/mrbysco/telepastries/blocks/cake/BlockCakeBase.java index e66e991..855b19f 100644 --- a/src/main/java/com/mrbysco/telepastries/blocks/cake/BlockCakeBase.java +++ b/src/main/java/com/mrbysco/telepastries/blocks/cake/BlockCakeBase.java @@ -53,6 +53,8 @@ public class BlockCakeBase extends BlockPastryBase { Block.box(11.0D, 0.0D, 1.0D, 15.0D, 8.0D, 15.0D), Block.box(13.0D, 0.0D, 1.0D, 15.0D, 8.0D, 15.0D)}; + protected static final CakeTeleporter TELEPORTER = new CakeTeleporter(); + public BlockCakeBase(BlockBehaviour.Properties properties) { super(properties.strength(0.5F).sound(SoundType.WOOL).randomTicks()); this.registerDefaultState(this.stateDefinition.any().setValue(BITES, Integer.valueOf(0))); @@ -181,9 +183,8 @@ public void teleportToDimension(LevelAccessor worldIn, BlockPos pos, Player play return; } - CakeTeleporter cakeTeleporter = new CakeTeleporter(destinationWorld); - CakeTeleporter.addDimensionPosition(serverPlayer, serverPlayer.level().dimension(), serverPlayer.blockPosition().offset(0, 1, 0)); - serverPlayer.changeDimension(destinationWorld, cakeTeleporter); + CakeTeleporter.addDimensionPosition(serverPlayer, serverPlayer.level().dimension(), serverPlayer.blockPosition()); + serverPlayer.changeDimension(destinationWorld, TELEPORTER); } } } diff --git a/src/main/java/com/mrbysco/telepastries/blocks/cake/BlockEndCake.java b/src/main/java/com/mrbysco/telepastries/blocks/cake/BlockEndCake.java index f0f469b..07bc900 100644 --- a/src/main/java/com/mrbysco/telepastries/blocks/cake/BlockEndCake.java +++ b/src/main/java/com/mrbysco/telepastries/blocks/cake/BlockEndCake.java @@ -36,9 +36,8 @@ public void teleportToDimension(LevelAccessor worldIn, BlockPos pos, Player play if (destinationWorld == null) return; - CakeTeleporter teleporter = new CakeTeleporter(destinationWorld); - CakeTeleporter.addDimensionPosition(serverPlayer, serverPlayer.level().dimension(), serverPlayer.blockPosition().offset(0, 1, 0)); - serverPlayer.changeDimension(destinationWorld, teleporter); + CakeTeleporter.addDimensionPosition(serverPlayer, serverPlayer.level().dimension(), serverPlayer.blockPosition()); + serverPlayer.changeDimension(destinationWorld, TELEPORTER); } } } diff --git a/src/main/java/com/mrbysco/telepastries/util/CakeTeleporter.java b/src/main/java/com/mrbysco/telepastries/util/CakeTeleporter.java index 72fb51c..f08ef24 100644 --- a/src/main/java/com/mrbysco/telepastries/util/CakeTeleporter.java +++ b/src/main/java/com/mrbysco/telepastries/util/CakeTeleporter.java @@ -1,17 +1,22 @@ package com.mrbysco.telepastries.util; +import com.mojang.datafixers.util.Pair; import com.mrbysco.telepastries.Reference; import com.mrbysco.telepastries.TelePastries; import com.mrbysco.telepastries.blocks.cake.BlockCakeBase; import com.mrbysco.telepastries.config.TeleConfig; +import it.unimi.dsi.fastutil.longs.Long2BooleanArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.Util; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.tags.BlockTags; import net.minecraft.util.Mth; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; @@ -21,108 +26,204 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.border.WorldBorder; -import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.portal.PortalInfo; import net.minecraft.world.phys.Vec3; import net.minecraftforge.common.util.ITeleporter; import net.minecraftforge.fml.ModList; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; -import java.util.Optional; import java.util.function.Function; public class CakeTeleporter implements ITeleporter { + private static final Object2ObjectMap, LevelTeleportFinder> LEVEL_TELEPORTERS = Util.make(new Object2ObjectOpenHashMap<>(), map -> + map.put(ServerLevel.END, (entity, destWorld, minMaxBounds, cacheMap) -> toEnd(entity, destWorld)) + ); + @Nullable @Override public PortalInfo getPortalInfo(Entity entity, ServerLevel destWorld, Function defaultPortalInfo) { - PortalInfo pos; + // First, check if there is already an existing position to pull from + // If the position doesn't exist, use the current entity's current position clamped between the build heights + BlockPos spawnPos = getDimensionPosition(entity, destWorld.dimension()); + + // Cache map so that we don't need to run checking the specific block position each time + Long2BooleanArrayMap safeLocation = new Long2BooleanArrayMap(); + + // Add compatibility bounds for checking the y-positions the entity can spawn + var minMaxBounds = customCompatBounds(destWorld).mapFirst(min -> Math.max(min, destWorld.getMinBuildHeight())).mapSecond(max -> Math.min(max, destWorld.getMaxBuildHeight())); + + // If spawn position exists, verify position is safe + if (spawnPos != null && destWorld.getBlockState(spawnPos.relative(Direction.DOWN)).isSolid() && isPositionSafe(entity, destWorld, spawnPos, safeLocation, minMaxBounds)) { + return postProcessAndMake(destWorld, spawnPos, entity); + } + + // Check level teleporter to determine portal info + @Nullable + PortalInfo levelInfo = LEVEL_TELEPORTERS.getOrDefault(destWorld.dimension(), CakeTeleporter::searchAroundAndDown).determineTeleportLocation(entity, destWorld, minMaxBounds, safeLocation); + if (levelInfo != null) { + return levelInfo; + } - pos = placeNearExistingCake(destWorld, entity, dimensionPosition(entity, destWorld), entity instanceof Player); - pos = customCompat(destWorld, BlockPos.containing(pos.pos), entity); - pos = moveToSafeCoords(destWorld, entity, pos != null ? BlockPos.containing(pos.pos) : dimensionPosition(entity, destWorld)); - return pos; + // If none of these positions work, use the entity's current position and spawn and safety ring around them + // If the entity's position isn't within the world bounds, use default coordinates instead (0, 70, 0) + BlockPos teleportPos = destWorld.getWorldBorder().isWithinBounds(entity.blockPosition()) + && minMaxBounds.getFirst() < entity.blockPosition().getY() - 1 + && minMaxBounds.getSecond() > entity.blockPosition().getY() + entity.getBbHeight() + 1 + ? entity.blockPosition() : new BlockPos(0, Math.max(minMaxBounds.getFirst(), 70), 0); + var halfWidth = entity.getBbWidth() / 2; + int minX = Mth.floor(teleportPos.getX() - halfWidth - 1), + minY = teleportPos.getY() - 1, + minZ = Mth.floor(teleportPos.getZ() - halfWidth - 1), + maxX = Mth.ceil(teleportPos.getX() + halfWidth + 1), + maxY = Mth.ceil(teleportPos.getY() + entity.getBbHeight() + 1), + maxZ = Mth.ceil(teleportPos.getZ() + halfWidth + 1); + for (var pedestalPos : BlockPos.betweenClosed(minX, minY, minZ, maxX, maxY, maxZ)) { + // Don't do anything if the position is outside the world border + if (!destWorld.getWorldBorder().isWithinBounds(pedestalPos)) continue; + + // Get the block position to check + BlockState pedestalState = destWorld.getBlockState(pedestalPos); + + // Don't do anything if the block is a cake + if (pedestalState.getBlock() instanceof BlockCakeBase) continue; + + // If the position is beneath the entity and isn't solid, set to obsidian + if(pedestalPos.getY() == minY && !pedestalState.isSolid()) destWorld.setBlockAndUpdate(pedestalPos, Blocks.OBSIDIAN.defaultBlockState()); + // Otherwise, if the position is surrounding the entity and isn't solid, set to cobblestone + else if ((pedestalPos.getY() == maxY || pedestalPos.getX() == minX || pedestalPos.getX() == maxX || pedestalPos.getZ() == minZ || pedestalPos.getZ() == maxZ) && !pedestalState.isSolid()) + destWorld.setBlockAndUpdate(pedestalPos, Blocks.COBBLESTONE.defaultBlockState()); + // Otherwise, just set to air if the entity can't spawn in it + else if (!pedestalState.getBlock().isPossibleToRespawnInThis(pedestalState)) destWorld.setBlockAndUpdate(pedestalPos, Blocks.AIR.defaultBlockState()); + } + + // Create info + return postProcessAndMake(destWorld, teleportPos, entity); } + /** + * Search eight blocks around the current block and check down to determine where to teleport. + * + * @param entity the entity attempting to spawn at the location + * @param destWorld the level the entity is teleporting to + * @param cacheMap a cache to prevent additional lookups to the position + * @param minMaxBounds the bounds of the y position the entity can spawn within + * @return the portal information to teleport to, or {@code null} if there is none + */ @Nullable - private static PortalInfo placeNearExistingCake(ServerLevel destWorld, Entity entity, BlockPos pos, boolean isPlayer) { - int i = 200; - BlockPos blockpos = pos; - boolean isToEnd = destWorld.dimension() == Level.END; - boolean isToOverworld = destWorld.dimension() == Level.OVERWORLD; - boolean isFromEnd = entity.level().dimension() == Level.END && isToOverworld; - - if (isToEnd) { - ServerLevel.makeObsidianPlatform(destWorld); - blockpos = ServerLevel.END_SPAWN_POINT; - - return new PortalInfo(new Vec3((double) blockpos.getX() + 0.5D, (double) blockpos.getY(), (double) blockpos.getZ() + 0.5D), entity.getDeltaMovement(), entity.getYRot(), entity.getXRot()); - } else { - blockpos = getDimensionPosition(entity, destWorld.dimension(), entity.blockPosition()); - if (blockpos == null) { - if (isFromEnd && isToOverworld) { - TelePastries.LOGGER.info("Couldn't locate a cake location, using spawn point instead"); - blockpos = destWorld.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destWorld.getSharedSpawnPos()); - if (isPlayer && entity instanceof ServerPlayer serverPlayer) { - BlockPos respawnPos = serverPlayer.getRespawnPosition(); - float respawnAngle = serverPlayer.getRespawnAngle(); - Optional optional; - if (serverPlayer != null && respawnPos != null) { - optional = Player.findRespawnPositionAndUseSpawnBlock(destWorld, respawnPos, respawnAngle, false, false); - } else { - optional = Optional.empty(); - } - - if (optional.isPresent()) { - BlockState blockstate = destWorld.getBlockState(respawnPos); - boolean blockIsRespawnAnchor = blockstate.is(Blocks.RESPAWN_ANCHOR); - Vec3 vector3d = optional.get(); - float f1; - if (!blockstate.is(BlockTags.BEDS) && !blockIsRespawnAnchor) { - f1 = respawnAngle; - } else { - Vec3 vector3d1 = Vec3.atBottomCenterOf(respawnPos).subtract(vector3d).normalize(); - f1 = (float) Mth.wrapDegrees(Mth.atan2(vector3d1.z, vector3d1.x) * (double) (180F / (float) Math.PI) - 90.0D); - } - float angle = f1; - blockpos = BlockPos.containing(vector3d.x, vector3d.y, vector3d.z); - return new PortalInfo(new Vec3((double) blockpos.getX() + 0.5D, (double) blockpos.getY(), (double) blockpos.getZ() + 0.5D), entity.getDeltaMovement(), angle, entity.getXRot()); - } - } - } else { - blockpos = entity.blockPosition(); - } + private static PortalInfo searchAroundAndDown(Entity entity, ServerLevel destWorld, Pair minMaxBounds, Long2BooleanArrayMap cacheMap) { + // Set y position to max possible + BlockPos spawnPos = entity.blockPosition().atY(Math.min(minMaxBounds.getSecond(), destWorld.getMinBuildHeight() + destWorld.getLogicalHeight()) - 1); + + // No spawn position or isn't valid, so loop around location + for (var checkPos: BlockPos.spiralAround(spawnPos, 16, Direction.EAST, Direction.SOUTH)) { + // Load chunk to actually check the location + destWorld.getChunk(checkPos); + + // Cycle through positions from top to bottom + for (int heightY = Math.min(spawnPos.getY(), destWorld.getHeight(Heightmap.Types.MOTION_BLOCKING, checkPos.getX(), checkPos.getZ())); heightY > minMaxBounds.getFirst(); --heightY) { + checkPos.setY(heightY); + + // Since we are checking going down, we want to verify the player is on the floor + // Check the player position afterward + if (!destWorld.getBlockState(checkPos.immutable().relative(Direction.DOWN)).isSolid() + || !isPositionSafe(entity, destWorld, checkPos, cacheMap, minMaxBounds) + ) continue; + + // All positions the entity is in is safe, so spawn in that location + return postProcessAndMake(destWorld, checkPos, entity); } } - if (blockpos.equals(BlockPos.ZERO)) { - return null; - } else { - return makePortalInfo(entity, blockpos.getX(), blockpos.getY(), blockpos.getZ()); - } + // If it fails, return null + return null; } - private BlockPos dimensionPosition(Entity entity, Level destWorld) { - boolean flag2 = destWorld.dimension() == Level.NETHER; - if (entity.level().dimension() != Level.NETHER && !flag2) { - return entity.blockPosition(); - } else { - WorldBorder worldborder = destWorld.getWorldBorder(); - double d0 = Math.max(-2.9999872E7D, worldborder.getMinX() + 16.0D); - double d1 = Math.max(-2.9999872E7D, worldborder.getMinZ() + 16.0D); - double d2 = Math.min(2.9999872E7D, worldborder.getMaxX() - 16.0D); - double d3 = Math.min(2.9999872E7D, worldborder.getMaxZ() - 16.0D); - double d4 = DimensionType.getTeleportationScale(entity.level().dimensionType(), destWorld.dimensionType()); - BlockPos blockpos1 = BlockPos.containing(Mth.clamp(entity.getX() * d4, d0, d2), entity.getY(), Mth.clamp(entity.getZ() * d4, d1, d3)); - - return blockpos1; + /** + * Set the portal information to the end's spawn point. + * + * @param entity the entity attempting to spawn at the location + * @param destWorld the level the entity is teleporting to + * @return the portal information to teleport to, or {@code null} if there is none + * + * @deprecated this should be removed in favor of a datagen solution + */ + @Deprecated + private static PortalInfo toEnd(Entity entity, ServerLevel destWorld) { + // Get teleport position + BlockPos teleportPos = ServerLevel.END_SPAWN_POINT; + + // Get space around entity and below + var halfWidth = entity.getBbWidth() / 2; + int minY = teleportPos.getY() - 1; + + // Spawn platform + for (var pedestalPos : BlockPos.betweenClosed( + Mth.floor(teleportPos.getX() - halfWidth), + minY, + Mth.floor(teleportPos.getZ() - halfWidth), + Mth.ceil(teleportPos.getX() + halfWidth), + Mth.ceil(teleportPos.getY() + entity.getBbHeight() + 1), + Mth.ceil(teleportPos.getZ() + halfWidth) + )) { + // Get the block position to check + BlockState pedestalState = destWorld.getBlockState(pedestalPos); + + // Don't do anything if the block is a cake + if (pedestalState.getBlock() instanceof BlockCakeBase) continue; + + // If the position is beneath the entity and isn't solid, set to obsidian + if(pedestalPos.getY() == minY && !pedestalState.isSolid()) destWorld.setBlockAndUpdate(pedestalPos, Blocks.OBSIDIAN.defaultBlockState()); + // Otherwise, just set to air if the entity can't spawn in it + else if (!pedestalState.getBlock().isPossibleToRespawnInThis(pedestalState)) destWorld.setBlockAndUpdate(pedestalPos, Blocks.AIR.defaultBlockState()); } + + return postProcessAndMake(destWorld, teleportPos, entity); } - public CakeTeleporter(ServerLevel worldIn) { + /** + * Checks if all blocks within the entity's bounding box is safe to spawn in. + * + * @param entity the entity attempting to spawn at the location + * @param destWorld the level the entity is teleporting to + * @param checkPos the position the entity is trying to be spawned at + * @param cacheMap a cache to prevent additional lookups to the position + * @param minMaxBounds the bounds of the y position the entity can spawn within + * @return {@code true} if it is safe for the entity to spawn here, {@code false} otherwise + */ + private static boolean isPositionSafe(Entity entity, ServerLevel destWorld, BlockPos checkPos, Long2BooleanArrayMap cacheMap, Pair minMaxBounds) { + var halfWidth = entity.getBbWidth() / 2; + // We construct the position based on the entity radius + // We could use the AABB method; however we want to account fo edge cases where the entity is touching a corner with + // the box, causing the safety check to fail and change the spawn position. + // This is also why we round to the higher or lower value + for (var entityBoxPos: BlockPos.betweenClosed( + Math.round(checkPos.getX() - halfWidth), + checkPos.getY(), + Math.round(checkPos.getZ() - halfWidth), + Math.round(checkPos.getX() + halfWidth), + Math.round(checkPos.getY() + entity.getBbHeight()), + Math.round(checkPos.getZ() + halfWidth) + )) { + // If a safe position isn't found in the entity is in, move to next spot to check + if (!cacheMap.computeIfAbsent( + entityBoxPos.asLong(), + c -> { + // Check if position is within bounds or that the position's min build height is higher than the spawning position + if (!destWorld.getWorldBorder().isWithinBounds(entityBoxPos) || minMaxBounds.getFirst() >= entityBoxPos.getY()) return false; + // Get block state and check if it is possible to respawn + BlockState entityBoxState = destWorld.getBlockState(entityBoxPos); + return entityBoxState.getBlock().isPossibleToRespawnInThis(entityBoxState); + } + )) { + return false; + } + } + + // If nothing fails, it is a safe location + return true; } @Override @@ -150,30 +251,21 @@ public static void addDimensionPosition(Entity entityIn, ResourceKey dim, entityData.put(Player.PERSISTED_NBT_TAG, data); } - public static BlockPos getDimensionPosition(Entity entityIn, ResourceKey dim, BlockPos position) { + @Nullable + public static BlockPos getDimensionPosition(Entity entityIn, ResourceKey dim) { CompoundTag entityData = entityIn.getPersistentData(); CompoundTag data = getTag(entityData); ResourceLocation dimLocation = dim.location(); - BlockPos dimPos = null; if (data.contains(Reference.MOD_PREFIX + dimLocation)) { - dimPos = BlockPos.of(data.getLong(Reference.MOD_PREFIX + dimLocation)); + BlockPos dimPos = BlockPos.of(data.getLong(Reference.MOD_PREFIX + dimLocation)); TelePastries.LOGGER.debug("Found {}'s position of {} to: {}", entityIn.getDisplayName().getContents(), dimLocation, dimPos); return dimPos; } TelePastries.LOGGER.debug("Could not find {}'s previous location. Using current location", entityIn.getDisplayName().getContents()); - return dimPos; - } - - public static boolean hasDimensionPosition(Entity entityIn, ResourceKey dim) { - CompoundTag entityData = entityIn.getPersistentData(); - CompoundTag data = getTag(entityData); - - TelePastries.LOGGER.debug("Checking if entity has position stored for : " + dim.location()); - return data.contains(Reference.MOD_PREFIX + dim.location()); + return null; } - private static CompoundTag getTag(CompoundTag tag) { if (tag == null || !tag.contains(Player.PERSISTED_NBT_TAG)) { return new CompoundTag(); @@ -181,39 +273,20 @@ private static CompoundTag getTag(CompoundTag tag) { return tag.getCompound(Player.PERSISTED_NBT_TAG); } - private static PortalInfo customCompat(ServerLevel destWorld, BlockPos pos, Entity entity) { - BlockPos blockpos = pos; - if (ModList.get().isLoaded("twilightforest")) { - ResourceKey twilightKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("twilightforest", "twilight_forest")); - if (destWorld.dimension() == twilightKey) { - if (entity instanceof ServerPlayer serverPlayer) { - serverPlayer.setRespawnPosition(twilightKey, pos, serverPlayer.getYRot(), true, false); - } - } - } - - if (ModList.get().isLoaded("lostcities")) { - ResourceKey lostCityKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("lostcities", "lostcity")); - if (destWorld.dimension() == lostCityKey) { - if (entity instanceof ServerPlayer serverPlayer) { - serverPlayer.setRespawnPosition(lostCityKey, pos, serverPlayer.getYRot(), true, false); - } - } - } - + /** + * Returns the minimum and maximum y values the entity can spawn between. + * + * @param destWorld the level the entity is teleporting to + * @return The bounded y values the entity must spawn in-between + */ + private static Pair customCompatBounds(ServerLevel destWorld) { ResourceLocation customLocation = ResourceLocation.tryParse(TeleConfig.COMMON.customCakeDimension.get()); if (customLocation != null) { ResourceKey customWorldKey = ResourceKey.create(Registries.DIMENSION, customLocation); if (destWorld.dimension() == customWorldKey) { int minY = TeleConfig.COMMON.customCakeMinY.get(); - if (blockpos.getY() < minY) { - blockpos = new BlockPos(blockpos.getX(), minY, blockpos.getZ()); - } int maxY = TeleConfig.COMMON.customCakeMaxY.get(); - if (blockpos.getY() > maxY) { - blockpos = new BlockPos(blockpos.getX(), maxY, blockpos.getZ()); - } - return makePortalInfo(entity, blockpos.getX(), blockpos.getY(), blockpos.getZ()); + return Pair.of(minY, maxY); } } @@ -222,14 +295,8 @@ private static PortalInfo customCompat(ServerLevel destWorld, BlockPos pos, Enti ResourceKey customWorldKey = ResourceKey.create(Registries.DIMENSION, customLocation2); if (destWorld.dimension() == customWorldKey) { int minY = TeleConfig.COMMON.customCake2MinY.get(); - if (blockpos.getY() < minY) { - blockpos = new BlockPos(blockpos.getX(), minY, blockpos.getZ()); - } int maxY = TeleConfig.COMMON.customCake2MaxY.get(); - if (blockpos.getY() > maxY) { - blockpos = new BlockPos(blockpos.getX(), maxY, blockpos.getZ()); - } - return makePortalInfo(entity, blockpos.getX(), blockpos.getY(), blockpos.getZ()); + return Pair.of(minY, maxY); } } @@ -238,83 +305,77 @@ private static PortalInfo customCompat(ServerLevel destWorld, BlockPos pos, Enti ResourceKey customWorldKey = ResourceKey.create(Registries.DIMENSION, customLocation3); if (destWorld.dimension() == customWorldKey) { int minY = TeleConfig.COMMON.customCake3MinY.get(); - if (blockpos.getY() < minY) { - blockpos = new BlockPos(blockpos.getX(), minY, blockpos.getZ()); - } int maxY = TeleConfig.COMMON.customCake3MaxY.get(); - if (blockpos.getY() > maxY) { - blockpos = new BlockPos(blockpos.getX(), maxY, blockpos.getZ()); - } - return makePortalInfo(entity, blockpos.getX(), blockpos.getY(), blockpos.getZ()); + return Pair.of(minY, maxY); } } - return makePortalInfo(entity, blockpos.getX(), blockpos.getY(), blockpos.getZ()); + return Pair.of(Integer.MIN_VALUE, Integer.MAX_VALUE); } - //Safety stuff - private static PortalInfo moveToSafeCoords(ServerLevel serverLevel, Entity entity, BlockPos pos) { - if (serverLevel.isEmptyBlock(pos.below())) { - int distance; - for (distance = 1; distance < 32; ++distance) { - BlockState belowState = serverLevel.getBlockState(pos.below(distance)); - if (belowState.getBlock().isPossibleToRespawnInThis(belowState)) { - break; - } - } - - if (distance > 4) { - makePlatform(serverLevel, pos); - } - } else { - BlockPos abovePos = pos.above(1); - BlockState aboveState = serverLevel.getBlockState(pos.above()); - BlockState aboveState2 = serverLevel.getBlockState(abovePos); - if (aboveState.getBlock().isPossibleToRespawnInThis(aboveState) && - aboveState2.getBlock().isPossibleToRespawnInThis(aboveState2)) { - return makePortalInfo(entity, abovePos.getX() + 0.5D, abovePos.getY(), abovePos.getZ() + 0.5D); - } - if (!serverLevel.isEmptyBlock(pos.below()) || !serverLevel.isEmptyBlock(pos)) { - makePlatform(serverLevel, abovePos); - return makePortalInfo(entity, abovePos.getX(), abovePos.getY(), abovePos.getZ()); + /** + * Sets the spawn location of the entity in compatible levels. + * + * @param destWorld the level the entity is teleporting to + * @param pos the position the entity is trying to be spawned at + * @param entity the entity attempting to spawn at the location + */ + private static PortalInfo postProcessAndMake(ServerLevel destWorld, BlockPos pos, Entity entity) { + // Set overworld back to respawn position when using cake. + if (destWorld.dimension() == Level.OVERWORLD) { + if (entity instanceof ServerPlayer serverPlayer) { + serverPlayer.setRespawnPosition(Level.OVERWORLD, pos, serverPlayer.getYRot(), true, false); } } - return makePortalInfo(entity, pos.getX(), pos.getY(), pos.getZ()); - } - - private static void makePlatform(ServerLevel world, BlockPos pos) { - int i = pos.getX(); - int j = pos.getY() - 2; - int k = pos.getZ(); - BlockPos.betweenClosed(i - 2, j + 1, k - 2, i + 2, j + 4, k + 2).forEach((blockPos) -> { - if (!(world.getBlockState(blockPos).getBlock() instanceof BlockCakeBase)) { - if (!world.getFluidState(blockPos).isEmpty() || world.getBlockState(blockPos).getDestroySpeed(world, blockPos) >= 0) { - world.setBlockAndUpdate(blockPos, Blocks.COBBLESTONE.defaultBlockState()); - } - } - }); - BlockPos.betweenClosed(i - 1, j + 1, k - 1, i + 1, j + 3, k + 1).forEach((blockPos) -> { - if (!(world.getBlockState(blockPos).getBlock() instanceof BlockCakeBase)) { - if (world.getBlockState(blockPos).getDestroySpeed(world, blockPos) >= 0) { - world.setBlockAndUpdate(blockPos, Blocks.AIR.defaultBlockState()); + if (ModList.get().isLoaded("twilightforest")) { + ResourceKey twilightKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("twilightforest", "twilight_forest")); + if (destWorld.dimension() == twilightKey) { + if (entity instanceof ServerPlayer serverPlayer) { + serverPlayer.setRespawnPosition(twilightKey, pos, serverPlayer.getYRot(), true, false); } } - }); - BlockPos.betweenClosed(i - 1, j, k - 1, i + 1, j, k + 1).forEach((blockPos) -> { - if (!(world.getBlockState(blockPos).getBlock() instanceof BlockCakeBase)) { - if (world.getBlockState(blockPos).getDestroySpeed(world, blockPos) >= 0) { - world.setBlockAndUpdate(blockPos, Blocks.OBSIDIAN.defaultBlockState()); + } + + if (ModList.get().isLoaded("lostcities")) { + ResourceKey lostCityKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("lostcities", "lostcity")); + if (destWorld.dimension() == lostCityKey) { + if (entity instanceof ServerPlayer serverPlayer) { + serverPlayer.setRespawnPosition(lostCityKey, pos, serverPlayer.getYRot(), true, false); } } - }); + } + + return makePortalInfo(entity, pos); } - private static PortalInfo makePortalInfo(Entity entity, double x, double y, double z) { - return makePortalInfo(entity, new Vec3(x, y, z)); + /** + * Creates the portal info based on the given block position. + * + * @param entity the entity attempting to spawn at the location + * @param pos the position the entity is trying to be spawned at + * @return the information necessary to teleport the entity + */ + private static PortalInfo makePortalInfo(Entity entity, BlockPos pos) { + return new PortalInfo(new Vec3(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5), Vec3.ZERO, entity.getYRot(), entity.getXRot()); } - private static PortalInfo makePortalInfo(Entity entity, Vec3 pos) { - return new PortalInfo(pos, Vec3.ZERO, entity.getYRot(), entity.getXRot()); + /** + * A location finder for determining where to teleport the entity in a given level. + */ + @FunctionalInterface + interface LevelTeleportFinder { + + /** + * Determine where to teleport the entity for the given level. + * + * @param entity the entity attempting to spawn at the location + * @param destWorld the level the entity is teleporting to + * @param cacheMap a cache to prevent additional lookups to the position + * @param minMaxBounds the bounds of the y position the entity can spawn within + * @return the portal information to teleport to, or {@code null} if there is none + */ + @Nullable + PortalInfo determineTeleportLocation(Entity entity, ServerLevel destWorld, Pair minMaxBounds, Long2BooleanArrayMap cacheMap); } }