Find alternate grounded age arrivals
Build / build (push) Canceled after 0s

This commit is contained in:
dnorberg committed 2026-09-21 09:37:06 -04:00
1 parent 67ce69e8b2
commit a9390c3e4c
5 files changed
+70 -7

No files matched your search

@@ -2,6 +2,7 @@ package net.dnorberg.rivencraft.book;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -9,12 +10,14 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.Vec3;
/** Server-side validation and execution for book travel. */
public final class LinkingService {
private static final int HORIZONTAL_SEARCH_RADIUS = 4;
private static final int VERTICAL_SEARCH_RADIUS = 2;
private static final int AGE_ARRIVAL_SEARCH_RADIUS = 64;
private LinkingService() {
}
@@ -25,7 +28,20 @@ public final class LinkingService {
/** Travels to an Age-style destination, which must have solid footing. */
public static LinkResult travelWithFooting(ServerPlayer player, LinkTarget target) {
return travel(player, target, true);
ServerLevel destinationLevel = player.getServer().getLevel(target.location().dimension());
if (destinationLevel == null) {
return LinkResult.MISSING_DIMENSION;
}
Optional<Vec3> destination = findFootedAgeDestination(destinationLevel, target.location().pos());
if (destination.isEmpty()) {
return LinkResult.NO_SAFE_DESTINATION;
}
Vec3 position = destination.get();
player.teleportTo(destinationLevel, position.x, position.y, position.z, target.yaw(), target.pitch());
player.resetFallDistance();
return LinkResult.SUCCESS;
}
private static LinkResult travel(ServerPlayer player, LinkTarget target, boolean requiresFooting) {
@@ -66,6 +82,42 @@ public final class LinkingService {
.findFirst();
}
/** Finds a footed arrival near an unsafe Age spawn without scanning the whole dimension. */
private static Optional<Vec3> findFootedAgeDestination(ServerLevel level, BlockPos origin) {
Optional<Vec3> localDestination = findSafeDestination(level, origin, true);
if (localDestination.isPresent()) {
return localDestination;
}
WorldBorder border = level.getWorldBorder();
return ageArrivalColumns(origin)
.flatMap(column -> surfaceCandidates(level, column))
.filter(level::isInWorldBounds)
.filter(border::isWithinBounds)
.filter(position -> isSafe(level, position, true))
.map(Vec3::atBottomCenterOf)
.findFirst();
}
private static Stream<BlockPos> ageArrivalColumns(BlockPos origin) {
return IntStream.rangeClosed(1, AGE_ARRIVAL_SEARCH_RADIUS)
.boxed()
.flatMap(radius -> BlockPos.betweenClosedStream(
origin.offset(-radius, 0, -radius),
origin.offset(radius, 0, radius)
)
.filter(position -> Math.max(Math.abs(position.getX() - origin.getX()),
Math.abs(position.getZ() - origin.getZ())) == radius)
)
.map(BlockPos::immutable);
}
private static Stream<BlockPos> surfaceCandidates(ServerLevel level, BlockPos column) {
int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, column.getX(), column.getZ());
BlockPos surface = new BlockPos(column.getX(), surfaceY, column.getZ());
return Stream.of(surface, surface.above(), surface.below(), surface.above(2), surface.below(2));
}
private static Stream<BlockPos> candidates(BlockPos origin) {
return BlockPos.betweenClosedStream(
origin.offset(-HORIZONTAL_SEARCH_RADIUS, -VERTICAL_SEARCH_RADIUS, -HORIZONTAL_SEARCH_RADIUS),
@@ -7,6 +7,7 @@ import net.dnorberg.rivencraft.item.DescriptiveBookItem;
import net.dnorberg.rivencraft.registry.ModDataComponents;
import net.dnorberg.rivencraft.registry.ModItems;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.GlobalPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
@@ -84,18 +85,24 @@ public final class LinkingGameTests {
}
@GameTest(template = "empty")
public static void descriptiveTravelRequiresFooting(GameTestHelper helper) {
public static void descriptiveTravelFindsAlternateFooting(GameTestHelper helper) {
BlockPos absoluteOrigin = helper.absolutePos(ORIGIN);
for (BlockPos position : BlockPos.betweenClosed(
absoluteOrigin.offset(-4, -3, -4), absoluteOrigin.offset(4, 3, 4))) {
absoluteOrigin.offset(-6, -3, -6), absoluteOrigin.offset(6, 3, 6))) {
helper.getLevel().setBlock(position, Blocks.AIR.defaultBlockState(), 2);
}
BlockPos alternateDestination = absoluteOrigin.offset(6, 0, 0);
helper.getLevel().setBlock(alternateDestination.below(), Blocks.STONE.defaultBlockState(), 2);
ServerPlayer player = helper.makeMockServerPlayerInLevel();
LinkTarget target = new LinkTarget(GlobalPos.of(helper.getLevel().dimension(), absoluteOrigin), 0.0F, 0.0F);
helper.assertValueEqual(LinkingService.travelWithFooting(player, target), LinkingService.LinkResult.NO_SAFE_DESTINATION,
"Descriptive Book travel must not place players without solid footing");
helper.assertValueEqual(LinkingService.travelWithFooting(player, target), LinkingService.LinkResult.SUCCESS,
"Descriptive Book travel must find an alternate grounded arrival");
BlockPos landing = player.blockPosition();
helper.assertTrue(helper.getLevel().getBlockState(landing.below()).isFaceSturdy(
helper.getLevel(), landing.below(), Direction.UP),
"The alternate arrival must have sturdy footing");
helper.succeed();
}