Compare commits

...
1 Commits
Author SHA1 Message Date
dnorberg 350d137d9e Render fallback crystal portals as two-sided surfaces
Build / build (push) Canceled after 0s
2026-10-18 00:22:56 -04:00
6 changed files with 64 additions and 5 deletions

No files matched your search

+1 -1
View File
@@ -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
@@ -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<CrystalPortalBlock> 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<Direction.Axis> 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<? extends BaseEntityBlock> codec() { return CODEC; }
@Override protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> 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
@@ -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();
@@ -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;
}
@@ -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"}}}
@@ -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"}}}]}