From aed8d6a5c3e0d7f0ec3874e1abf4b107c78ceed5 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Mon, 21 Sep 2026 09:03:59 -0400 Subject: [PATCH] Allow floorless leafy link destinations --- docs/PORTING.md | 3 +++ gradle.properties | 2 +- .../dnorberg/rivencraft/book/LinkingService.java | 15 ++++++++------- .../rivencraft/gametest/LinkingGameTests.java | 11 ++++++++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/PORTING.md b/docs/PORTING.md index 3f8d5b7..e4e9666 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -142,6 +142,9 @@ Version 0.1.20 uses a cubic 18-by-18-by-18 snapshot. The renderer remaps the link point within that cube so its existing target-centered camera framing is unchanged. +Version 0.1.21 treats air and leaf blocks as passable linking destinations; +links no longer require a solid floor beneath the player. + The preview code is separated behind a renderer interface and uses an isolated Ponder level populated from server-authoritative destination snapshots. diff --git a/gradle.properties b/gradle.properties index 9190884..35088e8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,7 +33,7 @@ mod_name=Rivencraft # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-3.0-or-later # The mod version. See https://semver.org/ -mod_version=0.1.20 +mod_version=0.1.21 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/net/dnorberg/rivencraft/book/LinkingService.java b/src/main/java/net/dnorberg/rivencraft/book/LinkingService.java index b6babd2..3368ec7 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/LinkingService.java +++ b/src/main/java/net/dnorberg/rivencraft/book/LinkingService.java @@ -4,9 +4,9 @@ import java.util.Comparator; import java.util.Optional; import java.util.stream.Stream; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; 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.phys.Vec3; @@ -63,12 +63,13 @@ public final class LinkingService { private static boolean isSafe(ServerLevel level, BlockPos feet) { BlockPos head = feet.above(); - BlockPos floor = feet.below(); - return level.getBlockState(feet).getCollisionShape(level, feet).isEmpty() - && level.getFluidState(feet).isEmpty() - && level.getBlockState(head).getCollisionShape(level, head).isEmpty() - && level.getFluidState(head).isEmpty() - && level.getBlockState(floor).isFaceSturdy(level, floor, Direction.UP); + return isPassableSpawnSpace(level, feet) && isPassableSpawnSpace(level, head); + } + + private static boolean isPassableSpawnSpace(ServerLevel level, BlockPos position) { + var state = level.getBlockState(position); + return level.getFluidState(position).isEmpty() + && (state.getCollisionShape(level, position).isEmpty() || state.is(BlockTags.LEAVES)); } public enum LinkResult { diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java index 28bbf08..5dc6033 100644 --- a/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java @@ -13,6 +13,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.gametest.GameTestHolder; import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; @@ -44,8 +45,10 @@ public final class LinkingGameTests { } @GameTest(template = "empty") - public static void travelUsesRecordedSafePosition(GameTestHelper helper) { - helper.setBlock(ORIGIN.below(), Blocks.STONE); + public static void travelUsesLeafyDestinationWithoutFloor(GameTestHelper helper) { + helper.setBlock(ORIGIN.below(), Blocks.AIR); + helper.setBlock(ORIGIN, Blocks.OAK_LEAVES); + helper.setBlock(ORIGIN.above(), Blocks.OAK_LEAVES); BlockPos destination = helper.absolutePos(ORIGIN); ServerPlayer player = helper.makeMockServerPlayerInLevel(); LinkTarget target = new LinkTarget(GlobalPos.of(helper.getLevel().dimension(), destination), 90.0F, 15.0F); @@ -53,8 +56,10 @@ public final class LinkingGameTests { LinkingService.LinkResult result = LinkingService.travel(player, target); helper.assertValueEqual(result, LinkingService.LinkResult.SUCCESS, - "A Linking Book must travel to a clear location with a sturdy floor"); + "A Linking Book must accept leaf blocks even without a floor"); helper.assertTrue(player.level() == helper.getLevel(), "Travel must remain in the recorded dimension"); + helper.assertTrue(player.position().distanceToSqr(Vec3.atBottomCenterOf(destination)) < 0.001D, + "The recorded leafy destination itself must be selected"); helper.assertTrue(Math.abs(player.getYRot() - 90.0F) < 0.01F && Math.abs(player.getXRot() - 15.0F) < 0.01F, "Travel must restore the recorded orientation"); helper.succeed();