diff --git a/gradle.properties b/gradle.properties index 601e7c6..0b70447 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,7 +36,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. Rivencraft uses the x.y.z.b policy documented in README.md. -mod_version=0.6.2.4 +mod_version=0.6.2.5 immersive_portals_version=6.0.7 terrablender_version=4.1.0.8 serene_seasons_version_id=B6nTjh4b diff --git a/src/main/java/net/dnorberg/rivencraft/block/CrystalPortalBlock.java b/src/main/java/net/dnorberg/rivencraft/block/CrystalPortalBlock.java index 61dc964..4bab10e 100644 --- a/src/main/java/net/dnorberg/rivencraft/block/CrystalPortalBlock.java +++ b/src/main/java/net/dnorberg/rivencraft/block/CrystalPortalBlock.java @@ -10,6 +10,7 @@ import net.dnorberg.rivencraft.portal.PortalTravelCooldown; import net.dnorberg.rivencraft.registry.ModBlocks; import net.dnorberg.rivencraft.registry.ModItems; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -24,6 +25,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; import net.minecraft.world.level.material.Fluid; import net.neoforged.fml.ModList; @@ -32,15 +34,16 @@ public final class CrystalPortalBlock extends BaseEntityBlock { public static final MapCodec CODEC = simpleCodec(CrystalPortalBlock::new); // Retained so portal blocks already saved with this state still deserialize. public static final BooleanProperty LIVE_VIEW = BooleanProperty.create("live_view"); + public static final EnumProperty AXIS = EnumProperty.create("axis", Direction.Axis.class); private static final String COOLDOWN = "RivencraftCrystalPortalCooldown"; public CrystalPortalBlock(Properties properties) { super(properties); - registerDefaultState(stateDefinition.any().setValue(LIVE_VIEW, false)); + registerDefaultState(stateDefinition.any().setValue(LIVE_VIEW, false).setValue(AXIS, Direction.Axis.Z)); } @Override protected MapCodec codec() { return CODEC; } @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(LIVE_VIEW); + builder.add(LIVE_VIEW, AXIS); } @Override protected RenderShape getRenderShape(BlockState state) { // The live-view flag can lag behind portal-entity creation or fail to diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/CrystalPortalGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/CrystalPortalGameTests.java index c72647e..ef74b5d 100644 --- a/src/main/java/net/dnorberg/rivencraft/gametest/CrystalPortalGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/gametest/CrystalPortalGameTests.java @@ -2,6 +2,7 @@ package net.dnorberg.rivencraft.gametest; import net.dnorberg.rivencraft.Rivencraft; import net.dnorberg.rivencraft.block.BookReceptacleBlock; +import net.dnorberg.rivencraft.block.CrystalPortalBlock; import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity; import net.dnorberg.rivencraft.block.entity.CrystalPortalBlockEntity; import net.dnorberg.rivencraft.book.LinkTarget; @@ -49,6 +50,8 @@ public final class CrystalPortalGameTests { BlockPos center = helper.absolutePos(new BlockPos(1, 2, 1)); helper.assertTrue(level.getBlockEntity(center) instanceof CrystalPortalBlockEntity portal && holderPos.equals(portal.receptacle()), "The portal must point to its one book holder"); + helper.assertValueEqual(level.getBlockState(center).getValue(CrystalPortalBlock.AXIS), Direction.Axis.Z, + "A north/south opening must only show its two player-facing sides"); CrystalPortalNetwork.shutdown(level, holderPos); helper.assertTrue(level.getBlockState(center).isAir(), "Removing the book must release the portal"); helper.succeed(); @@ -58,6 +61,48 @@ public final class CrystalPortalGameTests { return helper.absolutePos(new BlockPos(1, 2, 1)); } + @GameTest(template = "empty") + public static void eastWestOpeningOrientsFallbackSurface(GameTestHelper helper) { + var level = helper.getLevel(); + for (int z = 1; z <= 3; z++) for (int y = 1; y <= 3; y++) { + if (z == 2 && y == 2) continue; + level.setBlock(helper.absolutePos(new BlockPos(1, y, z)), + ModBlocks.CRYSTAL_BLOCK.get().defaultBlockState(), 3); + } + BlockPos holderPos = helper.absolutePos(new BlockPos(1, 1, 0)); + level.setBlock(holderPos, ModBlocks.BOOK_RECEPTACLE.get().defaultBlockState() + .setValue(BookReceptacleBlock.FACING, Direction.NORTH), 3); + ((BookReceptacleBlockEntity) level.getBlockEntity(holderPos)) + .setBook(new ItemStack(ModItems.LINKING_BOOK.get())); + helper.assertTrue(CrystalPortalNetwork.activate(level, holderPos, CrystalPortalNetwork.plan(level, holderPos)), + "The east/west crystal opening must activate"); + helper.assertValueEqual(level.getBlockState(helper.absolutePos(new BlockPos(1, 2, 2))) + .getValue(CrystalPortalBlock.AXIS), Direction.Axis.X, + "The fallback surface must face east and west, not north and south"); + helper.succeed(); + } + + @GameTest(template = "empty") + public static void horizontalOpeningOrientsFallbackSurface(GameTestHelper helper) { + var level = helper.getLevel(); + for (int x = 0; x < 3; x++) for (int z = 0; z < 3; z++) { + if (x == 1 && z == 1) continue; + level.setBlock(helper.absolutePos(new BlockPos(x, 1, z)), + ModBlocks.CRYSTAL_BLOCK.get().defaultBlockState(), 3); + } + BlockPos holderPos = helper.absolutePos(new BlockPos(0, 2, 1)); + level.setBlock(holderPos, ModBlocks.BOOK_RECEPTACLE.get().defaultBlockState() + .setValue(BookReceptacleBlock.FACING, Direction.UP), 3); + ((BookReceptacleBlockEntity) level.getBlockEntity(holderPos)) + .setBook(new ItemStack(ModItems.LINKING_BOOK.get())); + helper.assertTrue(CrystalPortalNetwork.activate(level, holderPos, CrystalPortalNetwork.plan(level, holderPos)), + "The horizontal crystal opening must activate"); + helper.assertValueEqual(level.getBlockState(helper.absolutePos(new BlockPos(1, 1, 1))) + .getValue(CrystalPortalBlock.AXIS), Direction.Axis.Y, + "The fallback surface must face up and down"); + helper.succeed(); + } + @GameTest(template = "empty") public static void incompleteFrameCannotActivate(GameTestHelper helper) { var level = helper.getLevel(); diff --git a/src/main/java/net/dnorberg/rivencraft/portal/CrystalPortalNetwork.java b/src/main/java/net/dnorberg/rivencraft/portal/CrystalPortalNetwork.java index afe28ec..c2195e3 100644 --- a/src/main/java/net/dnorberg/rivencraft/portal/CrystalPortalNetwork.java +++ b/src/main/java/net/dnorberg/rivencraft/portal/CrystalPortalNetwork.java @@ -7,6 +7,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import net.dnorberg.rivencraft.block.BookReceptacleBlock; +import net.dnorberg.rivencraft.block.CrystalPortalBlock; import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity; import net.dnorberg.rivencraft.block.entity.CrystalPortalBlockEntity; import net.dnorberg.rivencraft.registry.ModBlocks; @@ -119,6 +120,16 @@ public final class CrystalPortalNetwork { return false; } } + // All cells are now present, so the same bracing rule used for the + // Immersive Portals surface can orient the two-sided fallback model. + for (CrystalPortalSurfaces.Surface surface : CrystalPortalSurfaces.group(level, plan)) { + for (BlockPos pos : surface.cells()) { + BlockState state = level.getBlockState(pos); + Direction.Axis axis = surface.normal().getAxis(); + if (state.getValue(CrystalPortalBlock.AXIS) != axis) + level.setBlock(pos, state.setValue(CrystalPortalBlock.AXIS, axis), 2); + } + } return true; } diff --git a/src/main/resources/assets/rivencraft/blockstates/crystal_portal.json b/src/main/resources/assets/rivencraft/blockstates/crystal_portal.json index 6106e20..0b522f3 100644 --- a/src/main/resources/assets/rivencraft/blockstates/crystal_portal.json +++ b/src/main/resources/assets/rivencraft/blockstates/crystal_portal.json @@ -1 +1 @@ -{"variants":{"live_view=false":{"model":"rivencraft:block/crystal_portal"},"live_view=true":{"model":"rivencraft:block/crystal_portal"}}} +{"variants":{"axis=x,live_view=false":{"model":"rivencraft:block/crystal_portal","y":90},"axis=x,live_view=true":{"model":"rivencraft:block/crystal_portal","y":90},"axis=y,live_view=false":{"model":"rivencraft:block/crystal_portal","x":90},"axis=y,live_view=true":{"model":"rivencraft:block/crystal_portal","x":90},"axis=z,live_view=false":{"model":"rivencraft:block/crystal_portal"},"axis=z,live_view=true":{"model":"rivencraft:block/crystal_portal"}}} diff --git a/src/main/resources/assets/rivencraft/models/block/crystal_portal.json b/src/main/resources/assets/rivencraft/models/block/crystal_portal.json index 8e3c8a9..eaee8bf 100644 --- a/src/main/resources/assets/rivencraft/models/block/crystal_portal.json +++ b/src/main/resources/assets/rivencraft/models/block/crystal_portal.json @@ -1 +1 @@ -{"parent":"minecraft:block/cube_all","textures":{"all":"rivencraft:block/crystal_portal"}} +{"textures":{"portal":"rivencraft:block/crystal_portal"},"elements":[{"from":[0,0,7.5],"to":[16,16,8.5],"shade":false,"faces":{"north":{"texture":"#portal"},"south":{"texture":"#portal"}}}]}