From 9f4999c9668b0f3c5bba1b42a99c62fc3e7ba1a8 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 8 Jun 2023 23:05:28 +0200 Subject: [PATCH] [Change] Improved Eternal Portal Code --- .../betterend/blocks/EndPortalBlock.java | 121 +---- .../betterend/blocks/EternalPedestal.java | 15 +- .../betterend/commands/CommandRegistry.java | 6 +- .../mixin/common/portal/EntityMixin.java | 37 ++ .../betterend/portal/PortalBuilder.java | 380 ++++++++++++++ .../betterend/portal/TravelerState.java | 222 ++++++++ .../betterend/portal/TravelingEntity.java | 10 + .../betterend/registry/EndPoiTypes.java | 16 +- .../betterend/rituals/EternalRitual.java | 496 ++++-------------- .../resources/betterend.mixins.common.json | 3 +- 10 files changed, 775 insertions(+), 531 deletions(-) create mode 100644 src/main/java/org/betterx/betterend/mixin/common/portal/EntityMixin.java create mode 100644 src/main/java/org/betterx/betterend/portal/PortalBuilder.java create mode 100644 src/main/java/org/betterx/betterend/portal/TravelerState.java create mode 100644 src/main/java/org/betterx/betterend/portal/TravelingEntity.java diff --git a/src/main/java/org/betterx/betterend/blocks/EndPortalBlock.java b/src/main/java/org/betterx/betterend/blocks/EndPortalBlock.java index e338fc11..a388c22c 100644 --- a/src/main/java/org/betterx/betterend/blocks/EndPortalBlock.java +++ b/src/main/java/org/betterx/betterend/blocks/EndPortalBlock.java @@ -3,25 +3,15 @@ package org.betterx.betterend.blocks; import org.betterx.bclib.client.render.BCLRenderLayer; import org.betterx.bclib.interfaces.CustomColorProvider; import org.betterx.bclib.interfaces.RenderLayerProvider; -import org.betterx.betterend.advancements.BECriteria; -import org.betterx.betterend.interfaces.TeleportingEntity; +import org.betterx.betterend.portal.TravelingEntity; import org.betterx.betterend.registry.EndParticles; import org.betterx.betterend.registry.EndPortals; -import org.betterx.betterend.rituals.EternalRitual; import net.minecraft.client.color.block.BlockColor; import net.minecraft.client.color.item.ItemColor; import net.minecraft.core.BlockPos; -import net.minecraft.core.BlockPos.MutableBlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.Direction.Axis; -import net.minecraft.core.Direction.AxisDirection; -import net.minecraft.core.Registry; -import net.minecraft.core.registries.Registries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; @@ -31,19 +21,14 @@ import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.NetherPortalBlock; -import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.dimension.DimensionType; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import java.util.Objects; -import java.util.Optional; - public class EndPortalBlock extends NetherPortalBlock implements RenderLayerProvider, CustomColorProvider { public static final IntegerProperty PORTAL = EndBlockProperties.PORTAL; @@ -104,39 +89,16 @@ public class EndPortalBlock extends NetherPortalBlock implements RenderLayerProv return state; } + @Override public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - if (world.isClientSide || !validate(entity)) return; - entity.setPortalCooldown(); - ServerLevel currentWorld = (ServerLevel) world; - MinecraftServer server = currentWorld.getServer(); - ServerLevel targetWorld = EndPortals.getWorld(server, state.getValue(PORTAL)); - boolean isInEnd = currentWorld.dimension().equals(Level.END); - ServerLevel destination = isInEnd ? targetWorld : server.getLevel(Level.END); - BlockPos exitPos = findExitPos(currentWorld, destination, pos, entity); - if (exitPos == null) return; - if (entity instanceof ServerPlayer sp && sp.isCreative()) { - ((ServerPlayer) entity).teleportTo( - destination, - exitPos.getX() + 0.5, - exitPos.getY(), - exitPos.getZ() + 0.5, - entity.getYRot(), - entity.getXRot() - ); - BECriteria.PORTAL_TRAVEL.trigger(sp); - } else { - if (entity instanceof ServerPlayer sp) { - BECriteria.PORTAL_TRAVEL.trigger(sp); - } - ((TeleportingEntity) entity).be_setExitPos(exitPos); - Optional teleported = Optional.ofNullable(entity.changeDimension(destination)); - teleported.ifPresent(Entity::setPortalCooldown); + if (validate(entity) && entity instanceof TravelingEntity te && te.be_getTravelerState() != null) { + te.be_getTravelerState().handleInsidePortal(pos); } } private boolean validate(Entity entity) { - return !entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions() && !entity.isOnPortalCooldown(); + return !entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions(); } @Override @@ -144,79 +106,6 @@ public class EndPortalBlock extends NetherPortalBlock implements RenderLayerProv return BCLRenderLayer.TRANSLUCENT; } - private BlockPos findExitPos( - ServerLevel currentWorld, - ServerLevel targetWorld, - BlockPos currentPos, - Entity entity - ) { - if (targetWorld == null) return null; - Registry registry = targetWorld.registryAccess() - .registryOrThrow(Registries.DIMENSION_TYPE); - ResourceLocation targetWorldId = targetWorld.dimension().location(); - ResourceLocation currentWorldId = currentWorld.dimension().location(); - double targetMultiplier = Objects.requireNonNull(registry.get(targetWorldId)).coordinateScale(); - double currentMultiplier = Objects.requireNonNull(registry.get(currentWorldId)).coordinateScale(); - double multiplier = targetMultiplier > currentMultiplier ? 1.0 / targetMultiplier : currentMultiplier; - MutableBlockPos basePos = currentPos.mutable() - .set( - currentPos.getX() * multiplier, - currentPos.getY(), - currentPos.getZ() * multiplier - ); - MutableBlockPos checkPos = basePos.mutable(); - BlockState currentState = currentWorld.getBlockState(currentPos); - int radius = (EternalRitual.SEARCH_RADIUS >> 4) + 1; - checkPos = EternalRitual.findBlockPos( - targetWorld, - checkPos, - radius, - this, - state -> state.is(this) && state.getValue(PORTAL).equals(currentState.getValue(PORTAL)) - ); - if (checkPos != null) { - BlockState checkState = targetWorld.getBlockState(checkPos); - Axis axis = checkState.getValue(AXIS); - checkPos = findCenter(targetWorld, checkPos, axis); - Direction frontDir = Direction.fromAxisAndDirection(axis, AxisDirection.POSITIVE).getClockWise(); - Direction entityDir = entity.getMotionDirection(); - if (entityDir.getAxis().isVertical()) { - entityDir = frontDir; - } - if (frontDir != entityDir && frontDir.getOpposite() != entityDir) { - entity.rotate(Rotation.CLOCKWISE_90); - entityDir = entityDir.getClockWise(); - } - return checkPos.relative(entityDir); - } - return null; - } - - private MutableBlockPos findCenter(Level world, MutableBlockPos pos, Direction.Axis axis) { - return findCenter(world, pos, axis, 1); - } - - private MutableBlockPos findCenter(Level world, MutableBlockPos pos, Direction.Axis axis, int step) { - if (step > 8) return pos; - BlockState right, left; - Direction rightDir, leftDir; - rightDir = Direction.fromAxisAndDirection(axis, AxisDirection.POSITIVE); - leftDir = rightDir.getOpposite(); - right = world.getBlockState(pos.relative(rightDir)); - left = world.getBlockState(pos.relative(leftDir)); - BlockState down = world.getBlockState(pos.below()); - if (down.is(this)) { - return findCenter(world, pos.move(Direction.DOWN), axis, step); - } else if (right.is(this) && left.is(this)) { - return pos; - } else if (right.is(this)) { - return findCenter(world, pos.move(rightDir), axis, ++step); - } else if (left.is(this)) { - return findCenter(world, pos.move(leftDir), axis, ++step); - } - return pos; - } - @Override public BlockColor getProvider() { return (state, world, pos, tintIndex) -> EndPortals.getColor(state.getValue(PORTAL)); diff --git a/src/main/java/org/betterx/betterend/blocks/EternalPedestal.java b/src/main/java/org/betterx/betterend/blocks/EternalPedestal.java index 22de47e9..2a96e2cc 100644 --- a/src/main/java/org/betterx/betterend/blocks/EternalPedestal.java +++ b/src/main/java/org/betterx/betterend/blocks/EternalPedestal.java @@ -38,11 +38,11 @@ public class EternalPedestal extends PedestalBlock { } @Override - public void checkRitual(Level world, Player player, BlockPos pos) { - BlockEntity blockEntity = world.getBlockEntity(pos); + public void checkRitual(Level sourceLevel, Player player, BlockPos pos) { + BlockEntity blockEntity = sourceLevel.getBlockEntity(pos); if (blockEntity instanceof EternalPedestalEntity) { EternalPedestalEntity pedestal = (EternalPedestalEntity) blockEntity; - BlockState updatedState = world.getBlockState(pos); + BlockState updatedState = sourceLevel.getBlockState(pos); if (pedestal.isEmpty()) { if (pedestal.hasRitual()) { EternalRitual ritual = pedestal.getRitual(); @@ -57,16 +57,19 @@ public class EternalPedestal extends PedestalBlock { ritual.disablePortal(portalId); } } - world.setBlockAndUpdate(pos, updatedState.setValue(ACTIVATED, false).setValue(HAS_LIGHT, false)); + sourceLevel.setBlockAndUpdate(pos, updatedState.setValue(ACTIVATED, false).setValue(HAS_LIGHT, false)); } else { ItemStack itemStack = pedestal.getItem(0); ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemStack.getItem()); if (EndPortals.isAvailableItem(id)) { - world.setBlockAndUpdate(pos, updatedState.setValue(ACTIVATED, true).setValue(HAS_LIGHT, true)); + sourceLevel.setBlockAndUpdate( + pos, + updatedState.setValue(ACTIVATED, true).setValue(HAS_LIGHT, true) + ); if (pedestal.hasRitual()) { pedestal.getRitual().checkStructure(player); } else { - EternalRitual ritual = new EternalRitual(world, pos); + EternalRitual ritual = new EternalRitual(sourceLevel, pos); ritual.checkStructure(player); } } diff --git a/src/main/java/org/betterx/betterend/commands/CommandRegistry.java b/src/main/java/org/betterx/betterend/commands/CommandRegistry.java index 292862d6..7e44adb2 100644 --- a/src/main/java/org/betterx/betterend/commands/CommandRegistry.java +++ b/src/main/java/org/betterx/betterend/commands/CommandRegistry.java @@ -60,7 +60,11 @@ public class CommandRegistry { .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) .then(Commands.literal("locate_portal") .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) - .executes(ctx -> find_poi(ctx, EndPoiTypes.ETERNAL_PORTAL_INACTIVE)) + .executes(ctx -> find_poi(ctx, EndPoiTypes.ETERNAL_PORTAL)) + ) + .then(Commands.literal("locate_portal_frame") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(ctx -> find_poi(ctx, EndPoiTypes.ETERNAL_PORTAL_FRAME)) ) .then(Commands.literal("tpnext") .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) diff --git a/src/main/java/org/betterx/betterend/mixin/common/portal/EntityMixin.java b/src/main/java/org/betterx/betterend/mixin/common/portal/EntityMixin.java new file mode 100644 index 00000000..277de76e --- /dev/null +++ b/src/main/java/org/betterx/betterend/mixin/common/portal/EntityMixin.java @@ -0,0 +1,37 @@ +package org.betterx.betterend.mixin.common.portal; + +import org.betterx.betterend.portal.TravelerState; +import org.betterx.betterend.portal.TravelingEntity; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.portal.PortalInfo; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Entity.class) +public class EntityMixin implements TravelingEntity { + + private final TravelerState be_travelerState = TravelerState.init((net.minecraft.world.entity.Entity) (Object) this); + + public TravelerState be_getTravelerState() { + return be_travelerState; + } + + @Inject(method = "handleNetherPortal", at = @At("HEAD")) + void be_handleNetherPortal(CallbackInfo ci) { + if (be_travelerState != null) be_travelerState.portalTick(); + } + + @Inject(method = "findDimensionEntryPoint", at = @At("HEAD"), cancellable = true) + void be_findDimensionEntryPoint(ServerLevel serverLevel, CallbackInfoReturnable cir) { +// if (be_travelerState != null) { +// PortalInfo pi = be_travelerState.findDimensionEntryPoint(serverLevel); +// if (pi != null) cir.setReturnValue(pi); +// } + } +} diff --git a/src/main/java/org/betterx/betterend/portal/PortalBuilder.java b/src/main/java/org/betterx/betterend/portal/PortalBuilder.java new file mode 100644 index 00000000..59df5524 --- /dev/null +++ b/src/main/java/org/betterx/betterend/portal/PortalBuilder.java @@ -0,0 +1,380 @@ +package org.betterx.betterend.portal; + +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.betterend.blocks.EndPortalBlock; +import org.betterx.betterend.registry.EndBlocks; +import org.betterx.betterend.registry.EndFeatures; +import org.betterx.betterend.registry.EndPoiTypes; +import org.betterx.betterend.rituals.EternalRitual; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.BlockUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.Registries; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +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.levelgen.LegacyRandomSource; + +import com.google.common.collect.Sets; + +import java.awt.*; +import java.util.Optional; +import java.util.Set; + +public class PortalBuilder { + public final static Set FRAME_POSITIONS = Sets.newHashSet( + new Point(0, 0), + new Point(0, 6), + new Point(1, 0), + new Point(1, 6), + new Point(2, 1), + new Point(2, 5), + new Point(3, 2), + new Point(3, 3), + new Point(3, 4) + ); + public final static Set PORTAL_POSITIONS = Sets.newHashSet( + new Point(0, 0), + new Point(0, 1), + new Point(0, 2), + new Point(0, 3), + new Point(0, 4), + new Point(1, 0), + new Point(1, 1), + new Point(1, 2), + new Point(1, 3), + new Point(1, 4), + new Point(2, 1), + new Point(2, 2), + new Point(2, 3) + ); + private final static Set BASE_POSITIONS = Sets.newHashSet( + new Point(3, 0), + new Point(2, 0), + new Point(2, 1), + new Point(1, 1), + new Point(1, 2), + new Point(0, 1), + new Point(0, 2) + ); + private final static Block BASE = EndBlocks.FLAVOLITE.tiles; + public final static Block FRAME = EndBlocks.FLAVOLITE_RUNED_ETERNAL; + public final static Block PORTAL = EndBlocks.END_PORTAL_BLOCK; + public static int SPIRAL_SEARCH_RADIUS = 128; + private final ServerLevel targetLevel; + private final Level sourceLevel; + + public PortalBuilder(Level sourceLevel, ServerLevel targetLevel) { + this.targetLevel = targetLevel; + this.sourceLevel = sourceLevel; + } + + public static void generatePortal(Level world, BlockPos center, Direction.Axis axis, int portalId) { + BlockPos framePos = center.below(); + Direction moveDir = Direction.Axis.X == axis ? Direction.EAST : Direction.NORTH; + BlockState frame = FRAME.defaultBlockState().setValue(EternalRitual.ACTIVE, true); + FRAME_POSITIONS.forEach(point -> { + BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y); + world.setBlockAndUpdate(pos, frame); + pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); + world.setBlockAndUpdate(pos, frame); + }); + BlockState portal = PORTAL.defaultBlockState() + .setValue(EndPortalBlock.AXIS, axis) + .setValue(EndPortalBlock.PORTAL, portalId); + PORTAL_POSITIONS.forEach(point -> { + BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y); + world.setBlockAndUpdate(pos, portal); + pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); + world.setBlockAndUpdate(pos, portal); + }); + generateBase(world, framePos, moveDir); + } + + private static void generateBase(Level world, BlockPos center, Direction moveX) { + BlockState base = BASE.defaultBlockState(); + Direction moveY = moveX.getClockWise(); + BASE_POSITIONS.forEach(point -> { + BlockPos pos = center.mutable().move(moveX, point.x).move(moveY, point.y); + world.setBlockAndUpdate(pos, base); + pos = center.mutable().move(moveX, -point.x).move(moveY, point.y); + world.setBlockAndUpdate(pos, base); + pos = center.mutable().move(moveX, point.x).move(moveY, -point.y); + world.setBlockAndUpdate(pos, base); + pos = center.mutable().move(moveX, -point.x).move(moveY, -point.y); + world.setBlockAndUpdate(pos, base); + }); + } + + public static boolean checkIsAreaValid(Level world, BlockPos pos, Direction.Axis axis) { + if (pos.getY() >= world.getHeight() - 1) return false; + if (!isBaseValid(world, pos, axis)) return false; + return checkArea(world, pos, axis); + } + + private static boolean isBaseValid(Level world, BlockPos pos, Direction.Axis axis) { + boolean solid = true; + if (axis.equals(Direction.Axis.X)) { + pos = pos.below().offset(0, 0, -3); + for (int i = 0; i < 7; i++) { + BlockPos checkPos = pos.offset(0, 0, i); + BlockState state = world.getBlockState(checkPos); + solid &= validBlock(world, checkPos, state); + } + } else { + pos = pos.below().offset(-3, 0, 0); + for (int i = 0; i < 7; i++) { + BlockPos checkPos = pos.offset(i, 0, 0); + BlockState state = world.getBlockState(checkPos); + solid &= validBlock(world, checkPos, state); + } + } + return solid; + } + + private static boolean validBlock(Level world, BlockPos pos, BlockState state) { + return state.isRedstoneConductor(world, pos) && state.isCollisionShapeFullBlock(world, pos); + } + + public static boolean checkArea(Level world, BlockPos center, Direction.Axis axis) { + Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; + for (BlockPos checkPos : BlockPos.betweenClosed( + center.relative(moveDir.getClockWise()), + center.relative(moveDir.getCounterClockWise()) + )) { + for (Point point : PORTAL_POSITIONS) { + BlockPos pos = checkPos.mutable().move(moveDir, point.x).move(Direction.UP, point.y); + BlockState state = world.getBlockState(pos); + if (isStateInvalid(state)) return false; + pos = checkPos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); + state = world.getBlockState(pos); + if (isStateInvalid(state)) return false; + } + } + return true; + } + + private static boolean isStateInvalid(BlockState state) { + if (!state.getFluidState().isEmpty()) return true; + return !BlocksHelper.replaceableOrPlant(state); + } + + public Optional findPortalAround(BlockPos blockPos, WorldBorder worldBorder) { + PoiManager poiManager = this.targetLevel.getPoiManager(); + poiManager.ensureLoadedAndValid(this.targetLevel, blockPos, SPIRAL_SEARCH_RADIUS); + + Optional oPos = EndPoiTypes.ETERNAL_PORTAL.findPoiAround( + this.targetLevel, + blockPos, + SPIRAL_SEARCH_RADIUS, + worldBorder + ); + + return oPos.map(poiPos -> { + this.targetLevel.getChunkSource().addRegionTicket(TicketType.PORTAL, new ChunkPos(poiPos), 3, poiPos); + BlockState blockState = this.targetLevel.getBlockState(poiPos); + return BlockUtil.getLargestRectangleAround( + poiPos, + blockState.getValue(BlockStateProperties.HORIZONTAL_AXIS), + 21, + Direction.Axis.Y, + 21, + bp -> this.targetLevel.getBlockState(bp) == blockState + ); + }); + } + + public static BlockPos getStartingPos( + Level sourceLevel, + Level targetLevel, + Entity entity, + WorldBorder worldBorder + ) { + final double dimensionScale = DimensionType.getTeleportationScale( + sourceLevel.dimensionType(), + targetLevel.dimensionType() + ); + return worldBorder.clampToBounds( + entity.getX() * dimensionScale, + entity.getY(), + entity.getZ() * dimensionScale + ); + } + + public Optional createPortal( + BlockPos startPosition, + Direction.Axis axis, + int portalID + ) { + Direction portalDirection = Direction.get(Direction.AxisDirection.POSITIVE, axis); + double d = -1.0; + BlockPos centerPos = null; + double e = -1.0; + BlockPos blockPos3 = null; + final WorldBorder worldBorder = this.targetLevel.getWorldBorder(); + + final int maxHeight = Math.min( + this.targetLevel.getMaxBuildHeight(), + this.targetLevel.getMinBuildHeight() + this.targetLevel.getLogicalHeight() + ) - 1; + + BlockPos.MutableBlockPos currentPos = startPosition.mutable(); + for (BlockPos.MutableBlockPos testPosition : BlockPos.spiralAround( + startPosition, SPIRAL_SEARCH_RADIUS, Direction.EAST, Direction.SOUTH + )) { + final int levelHeight = Math.min( + maxHeight, + this.targetLevel.getHeight( + Heightmap.Types.MOTION_BLOCKING, + testPosition.getX(), + testPosition.getZ() + ) + ); + + // Check if the portal is within the world border + if (!worldBorder.isWithinBounds(testPosition) + || !worldBorder.isWithinBounds(testPosition.move(portalDirection, 1))) + continue; + + testPosition.move(portalDirection.getOpposite(), 1); + for (int yy = levelHeight; yy >= this.targetLevel.getMinBuildHeight(); --yy) { + int n; + testPosition.setY(yy); + if (!this.canPortalReplaceBlock(testPosition)) continue; + int startY = yy; + while (yy > this.targetLevel.getMinBuildHeight() && this.canPortalReplaceBlock(testPosition.move( + Direction.DOWN))) { + --yy; + } + if (yy + 4 > maxHeight || (n = startY - yy) > 0 && n < 3) continue; + testPosition.setY(yy); + if (!this.canHostFrame(testPosition, currentPos, portalDirection, 0)) continue; + double f = startPosition.distSqr(testPosition); + if (this.canHostFrame(testPosition, currentPos, portalDirection, -1) && this.canHostFrame( + testPosition, + currentPos, + portalDirection, + 1 + ) && (d == -1.0 || d > f)) { + d = f; + centerPos = testPosition.immutable(); + } + if (d != -1.0 || e != -1.0 && !(e > f)) continue; + e = f; + blockPos3 = testPosition.immutable(); + } + } + if (d == -1.0 && e != -1.0) { + centerPos = blockPos3; + d = e; + } + if (d == -1.0) { + int p = maxHeight - 9; + int o = Math.max(this.targetLevel.getMinBuildHeight() - -1, 70); + if (p < o) { + return Optional.empty(); + } + centerPos = new BlockPos( + startPosition.getX(), + Mth.clamp(startPosition.getY(), o, p), + startPosition.getZ() + ).immutable(); + //Direction direction2 = portalDirection.getClockWise(); + if (!worldBorder.isWithinBounds(centerPos)) { + return Optional.empty(); + } + } + buildPortal(portalDirection, centerPos, portalID); + + return Optional.of(centerPos.immutable()); + } + + private void buildPortal(Direction portalDirection, BlockPos centerPos, int portalID) { + Direction.Axis portalAxis = (Direction.Axis.X == portalDirection.getAxis()) + ? Direction.Axis.Z + : Direction.Axis.X; + if (!checkIsAreaValid(targetLevel, centerPos, portalAxis)) { + if (targetLevel.dimension() == Level.END) { + WorldBootstrap.getLastRegistryAccess() + .registryOrThrow(Registries.CONFIGURED_FEATURE) + .get(net.minecraft.data.worldgen.features.EndFeatures.END_ISLAND) + .place( + targetLevel, + targetLevel.getChunkSource().getGenerator(), + new LegacyRandomSource(centerPos.asLong()), + centerPos.below() + ); + } else if (targetLevel.dimension() == Level.OVERWORLD) { + centerPos = centerPos + .mutable() + .setY(targetLevel.getChunk(centerPos) + .getHeight( + Heightmap.Types.WORLD_SURFACE, + centerPos.getX(), + centerPos.getZ() + ) + 1); + } + EndFeatures.BIOME_ISLAND + .getPlacedFeature() + .value() + .place( + targetLevel, + targetLevel.getChunkSource().getGenerator(), + new LegacyRandomSource(centerPos.asLong()), + centerPos.below() + ); + + } + generatePortal(targetLevel, centerPos, portalAxis, portalID); + } + + private int getPortalID(BlockPos portalEntrancePos) { + BlockState currentState = this.sourceLevel.getBlockState(portalEntrancePos); + int portalID = currentState.hasProperty(EndPortalBlock.PORTAL) + ? currentState.getValue(EndPortalBlock.PORTAL) + : -1; + return portalID; + } + + private boolean canPortalReplaceBlock(BlockPos currentPos) { + BlockState blockState = this.targetLevel.getBlockState(currentPos); + return blockState.canBeReplaced() && blockState.getFluidState().isEmpty(); + } + + private boolean canHostFrame( + BlockPos pos, + BlockPos.MutableBlockPos currentPos, + Direction direction, + int widthScale + ) { + Direction orthogonalDir = direction.getClockWise(); + for (int x = -1; x < 3; ++x) { + for (int y = -1; y < 4; ++y) { + currentPos.setWithOffset( + pos, + direction.getStepX() * x + orthogonalDir.getStepX() * widthScale, + y, + direction.getStepZ() * x + orthogonalDir.getStepZ() * widthScale + ); + if (y < 0 && !this.targetLevel.getBlockState(currentPos).isSolid()) { + return false; + } + if (y < 0 || this.canPortalReplaceBlock(currentPos)) continue; + return false; + } + } + return true; + } +} diff --git a/src/main/java/org/betterx/betterend/portal/TravelerState.java b/src/main/java/org/betterx/betterend/portal/TravelerState.java new file mode 100644 index 00000000..fd912b7b --- /dev/null +++ b/src/main/java/org/betterx/betterend/portal/TravelerState.java @@ -0,0 +1,222 @@ +package org.betterx.betterend.portal; + +import org.betterx.betterend.BetterEnd; +import org.betterx.betterend.advancements.BECriteria; + +import net.minecraft.BlockUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.portal.PortalInfo; +import net.minecraft.world.level.portal.PortalShape; +import net.minecraft.world.phys.Vec3; + +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public final class TravelerState { + public final Entity entity; + private BlockPos portalEntrancePos; + private boolean isInsidePortal; + private int portalTime; + + public TravelerState(Entity entity) { + this.entity = entity; + this.portalEntrancePos = null; + this.isInsidePortal = false; + } + + public static TravelerState init(Entity e) { + return new TravelerState(e); + } + + public void handleInsidePortal(BlockPos blockPos) { + if (entity.isOnPortalCooldown()) { + entity.setPortalCooldown(); + return; + } + if (!this.level().isClientSide && !blockPos.equals(this.portalEntrancePos)) { + this.portalEntrancePos = blockPos.immutable(); + } + this.isInsidePortal = true; + } + + public Level level() { + return this.entity.level(); + } + + @ApiStatus.Internal + public void portalTick() { + if (!(this.level() instanceof ServerLevel)) { + return; + } + + if (this.isInsidePortal) { + final int waitTimer = entity.getPortalWaitTime(); + final ServerLevel sourceDimension = (ServerLevel) this.level(); + + MinecraftServer minecraftServer = sourceDimension.getServer(); + ServerLevel targetDimension = minecraftServer.getLevel(this.level().dimension() == Level.END + ? Level.OVERWORLD + : Level.END); + + if (targetDimension != null && !entity.isPassenger() && this.portalTime++ >= waitTimer) { + this.level().getProfiler().push("end_portal"); + this.portalTime = waitTimer; + entity.setPortalCooldown(); + + this.changeDimension(targetDimension); + this.level().getProfiler().pop(); + } + this.isInsidePortal = false; + } else { + if (this.portalTime > 0) { + this.portalTime -= 4; + } + if (this.portalTime < 0) { + this.portalTime = 0; + } + } + } + + @Nullable + public void changeDimension(ServerLevel targetLevel) { + if (!(this.level() instanceof ServerLevel) || this.entity.isRemoved()) { + return; + } + this.level().getProfiler().push("be_findEntry"); + this.entity.unRide(); + + //EternalRitual.findRitualForActivePortal(this.level(), portalEntrancePos); + PortalInfo portalInfo = this.findDimensionEntryPoint(targetLevel); + if (portalInfo == null) { + return; + } + this.level().getProfiler().push("be_reposition"); + teleportEntity(targetLevel, portalInfo); + + this.level().getProfiler().pop(); + this.level().getProfiler().pop(); + return; + } + + private void teleportEntity(ServerLevel serverLevel, PortalInfo portalInfo) { + final boolean targetIsEnd = serverLevel.dimension().equals(Level.END); + final MinecraftServer server = serverLevel.getServer(); + final ServerLevel destination = targetIsEnd ? server.getLevel(Level.END) : server.getLevel(Level.OVERWORLD); + if (entity instanceof ServerPlayer sp && sp.isCreative()) { + sp.teleportTo( + destination, + portalInfo.pos.x + 0.5, + portalInfo.pos.y, + portalInfo.pos.z + 0.5, + entity.getYRot() + 180, + entity.getXRot() + ); + BECriteria.PORTAL_TRAVEL.trigger(sp); + } else { + if (entity instanceof ServerPlayer sp) { + BECriteria.PORTAL_TRAVEL.trigger(sp); + } + entity.setPortalCooldown(); + } + } + + // /execute in the_end run tp 849 84 891 + // /execute in overworld run tp 849 64 891 + @Nullable + private PortalInfo findDimensionEntryPoint(ServerLevel targetLevel) { + boolean toEnd = targetLevel.dimension() == Level.END; + if (this.level().dimension() != Level.END && !toEnd) { + return null; + } + final WorldBorder worldBorder = targetLevel.getWorldBorder(); + final BlockPos startingPos = PortalBuilder.getStartingPos(this.level(), targetLevel, entity, worldBorder); + + return this.getExitPortal(targetLevel, startingPos, toEnd, worldBorder).map(foundRectangle -> { + Vec3 vec3; + Direction.Axis axis; + BlockState blockState = this.level().getBlockState(this.portalEntrancePos); + if (blockState.hasProperty(BlockStateProperties.HORIZONTAL_AXIS)) { + axis = blockState.getValue(BlockStateProperties.HORIZONTAL_AXIS); + BlockUtil.FoundRectangle foundRectangle2 = BlockUtil.getLargestRectangleAround( + this.portalEntrancePos, + axis, + 21, + Direction.Axis.Y, + 21, + blockPos -> this.level().getBlockState(blockPos) == blockState + ); + vec3 = this.getRelativePortalPosition(axis, foundRectangle2); + } else { + axis = Direction.Axis.X; + vec3 = new Vec3(0.5, 0.0, 0.0); + } + return PortalShape.createPortalInfo( + targetLevel, foundRectangle, axis, vec3, + this.entity, entity.getDeltaMovement(), entity.getYRot(), entity.getXRot() + ); + }).orElse(null); + } + + protected Optional getExitPortal( + ServerLevel targetLevel, + BlockPos startingPos, + boolean bl, + WorldBorder worldBorder + ) { + final PortalBuilder builder = new PortalBuilder(this.level(), targetLevel); + final Optional portalRectangle = builder.findPortalAround( + startingPos, + worldBorder + ); + if (portalRectangle.isPresent()) { + return portalRectangle; + } + + BetterEnd.LOGGER.error("Unable to locate an active portal"); + return portalRectangle; + } + + protected Vec3 getRelativePortalPosition(Direction.Axis axis, BlockUtil.FoundRectangle foundRectangle) { + return PortalShape.getRelativePosition( + foundRectangle, + axis, + entity.position(), + entity.getDimensions(entity.getPose()) + ); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (TravelerState) obj; + return Objects.equals(this.entity, that.entity) && + Objects.equals(this.portalEntrancePos, that.portalEntrancePos) && + this.isInsidePortal == that.isInsidePortal; + } + + @Override + public int hashCode() { + return Objects.hash(entity, portalEntrancePos, isInsidePortal); + } + + @Override + public String toString() { + return "TravelerState[" + + "entity=" + entity + ", " + + "portalEntrancePos=" + portalEntrancePos + ", " + + "isInsidePortal=" + isInsidePortal + ']'; + } + +} diff --git a/src/main/java/org/betterx/betterend/portal/TravelingEntity.java b/src/main/java/org/betterx/betterend/portal/TravelingEntity.java new file mode 100644 index 00000000..7fc74586 --- /dev/null +++ b/src/main/java/org/betterx/betterend/portal/TravelingEntity.java @@ -0,0 +1,10 @@ +package org.betterx.betterend.portal; + +/** + * Manages state for entities traveling through end portals. + */ +public interface TravelingEntity { + TravelerState be_getTravelerState(); + + +} diff --git a/src/main/java/org/betterx/betterend/registry/EndPoiTypes.java b/src/main/java/org/betterx/betterend/registry/EndPoiTypes.java index 6d381ec5..33a1a6d8 100644 --- a/src/main/java/org/betterx/betterend/registry/EndPoiTypes.java +++ b/src/main/java/org/betterx/betterend/registry/EndPoiTypes.java @@ -5,18 +5,22 @@ import org.betterx.bclib.api.v2.poi.PoiManager; import org.betterx.betterend.BetterEnd; import org.betterx.betterend.blocks.RunedFlavolite; +import com.google.common.collect.ImmutableSet; + import java.util.Set; public class EndPoiTypes { - public static final BCLPoiType ETERNAL_PORTAL_INACTIVE = PoiManager.register( - BetterEnd.makeID("eternal_portal_inactive"), - Set.of(EndBlocks.FLAVOLITE_RUNED_ETERNAL.defaultBlockState().setValue(RunedFlavolite.ACTIVATED, false)), + public static final BCLPoiType ETERNAL_PORTAL = PoiManager.register( + BetterEnd.makeID("eternal_portal"), + ImmutableSet.copyOf(EndBlocks.END_PORTAL_BLOCK.getStateDefinition().getPossibleStates()), 0, 1 ); - public static final BCLPoiType ETERNAL_PORTAL_ACTIVE = PoiManager.register( - BetterEnd.makeID("eternal_portal_active"), - Set.of(EndBlocks.FLAVOLITE_RUNED_ETERNAL.defaultBlockState().setValue(RunedFlavolite.ACTIVATED, true)), + public static final BCLPoiType ETERNAL_PORTAL_FRAME = PoiManager.register( + BetterEnd.makeID("eternal_portal_frame"), + Set.of( + EndBlocks.FLAVOLITE_RUNED_ETERNAL.defaultBlockState().setValue(RunedFlavolite.ACTIVATED, false) + ), 0, 1 ); diff --git a/src/main/java/org/betterx/betterend/rituals/EternalRitual.java b/src/main/java/org/betterx/betterend/rituals/EternalRitual.java index e14945f4..b6a0888b 100644 --- a/src/main/java/org/betterx/betterend/rituals/EternalRitual.java +++ b/src/main/java/org/betterx/betterend/rituals/EternalRitual.java @@ -1,49 +1,37 @@ package org.betterx.betterend.rituals; import org.betterx.bclib.blocks.BlockProperties; -import org.betterx.bclib.util.BlocksHelper; import org.betterx.betterend.BetterEnd; import org.betterx.betterend.advancements.BECriteria; import org.betterx.betterend.blocks.EndPortalBlock; import org.betterx.betterend.blocks.RunedFlavolite; import org.betterx.betterend.blocks.entities.EternalPedestalEntity; +import org.betterx.betterend.portal.PortalBuilder; import org.betterx.betterend.registry.EndBlocks; -import org.betterx.betterend.registry.EndFeatures; -import org.betterx.betterend.registry.EndPoiTypes; import org.betterx.betterend.registry.EndPortals; -import org.betterx.worlds.together.world.event.WorldBootstrap; +import net.minecraft.BlockUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.core.QuartPos; -import net.minecraft.core.Registry; import net.minecraft.core.particles.BlockParticleOption; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; -import net.minecraft.world.entity.ai.village.poi.PoiManager; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.NetherPortalBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.chunk.LevelChunkSection; -import net.minecraft.world.level.dimension.DimensionType; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.border.WorldBorder; import com.google.common.collect.Sets; @@ -51,11 +39,10 @@ import java.awt.*; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; public class EternalRitual { - private final static Set STRUCTURE_MAP = Sets.newHashSet( + private final static Set PEDESTAL_POSITIONS = Sets.newHashSet( new Point(-4, -5), new Point(-4, 5), new Point(-6, 0), @@ -63,49 +50,9 @@ public class EternalRitual { new Point(4, 5), new Point(6, 0) ); - private final static Set FRAME_MAP = Sets.newHashSet( - new Point(0, 0), - new Point(0, 6), - new Point(1, 0), - new Point(1, 6), - new Point(2, 1), - new Point(2, 5), - new Point(3, 2), - new Point(3, 3), - new Point(3, 4) - ); - private final static Set PORTAL_MAP = Sets.newHashSet( - new Point(0, 0), - new Point(0, 1), - new Point(0, 2), - new Point(0, 3), - new Point(0, 4), - new Point(1, 0), - new Point(1, 1), - new Point(1, 2), - new Point(1, 3), - new Point(1, 4), - new Point(2, 1), - new Point(2, 2), - new Point(2, 3) - ); - private final static Set BASE_MAP = Sets.newHashSet( - new Point(3, 0), - new Point(2, 0), - new Point(2, 1), - new Point(1, 1), - new Point(1, 2), - new Point(0, 1), - new Point(0, 2) - ); - private final static Block BASE = EndBlocks.FLAVOLITE.tiles; private final static Block PEDESTAL = EndBlocks.ETERNAL_PEDESTAL; - private final static Block FRAME = EndBlocks.FLAVOLITE_RUNED_ETERNAL; - private final static Block PORTAL = EndBlocks.END_PORTAL_BLOCK; - private final static BooleanProperty ACTIVE = BlockProperties.ACTIVE; - - public final static int SEARCH_RADIUS = calculateSearchSteps(48); + public final static BooleanProperty ACTIVE = BlockProperties.ACTIVE; private Level world; private Direction.Axis axis; @@ -148,7 +95,7 @@ public class EternalRitual { } boolean valid = checkFrame(world, center.below()); Item item = null; - for (Point pos : STRUCTURE_MAP) { + for (Point pos : PEDESTAL_POSITIONS) { BlockPos.MutableBlockPos checkPos = center.mutable(); checkPos.move(moveX, pos.x).move(moveY, pos.y); valid &= isActive(checkPos); @@ -165,7 +112,7 @@ public class EternalRitual { } } if (valid && item != null) { - activatePortal(item); + activatePortal(player, item); if (player instanceof ServerPlayer sp) { BECriteria.PORTAL_ON.trigger(sp); } @@ -175,7 +122,7 @@ public class EternalRitual { private boolean checkFrame(Level world, BlockPos framePos) { Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; boolean valid = true; - for (Point point : FRAME_MAP) { + for (Point point : PortalBuilder.FRAME_POSITIONS) { BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y); BlockState state = world.getBlockState(pos); valid &= state.getBlock() instanceof RunedFlavolite; @@ -190,7 +137,7 @@ public class EternalRitual { return active; } - private void activatePortal(Item keyItem) { + private void activatePortal(Player player, Item keyItem) { if (active) return; ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(keyItem); int portalId = EndPortals.getPortalIdByItem(itemId); @@ -198,13 +145,13 @@ public class EternalRitual { ResourceLocation worldId = targetWorld.dimension().location(); try { if (exit == null) { - initPortal(worldId, portalId); + initPortal(player, worldId, portalId); } else { if (!worldId.equals(targetWorldId)) { - initPortal(worldId, portalId); + initPortal(player, worldId, portalId); } else if (!checkFrame(targetWorld, exit.below())) { Direction.Axis portalAxis = (Direction.Axis.X == axis) ? Direction.Axis.Z : Direction.Axis.X; - generatePortal(targetWorld, exit, portalAxis, portalId); + PortalBuilder.generatePortal(targetWorld, exit, portalAxis, portalId); } activatePortal(targetWorld, exit, portalId); } @@ -219,9 +166,27 @@ public class EternalRitual { } } - private void initPortal(ResourceLocation worldId, int portalId) { + private void initPortal(Player player, ResourceLocation worldId, int portalId) { targetWorldId = worldId; - exit = findPortalPos(portalId); + if (world instanceof ServerLevel sourceWorld) { + ServerLevel targetLevel = (ServerLevel) getTargetWorld(portalId); + PortalBuilder builder = new PortalBuilder(world, targetLevel); + final WorldBorder worldBorder = targetLevel.getWorldBorder(); + final Optional foundRectangle = builder.findPortalAround( + this.center, + worldBorder + ); + + if (!foundRectangle.isPresent()) { + Optional centerPos; + centerPos = builder.createPortal( + PortalBuilder.getStartingPos(sourceWorld, targetLevel, player, worldBorder), + axis, + portalId + ); + centerPos.ifPresent(blockPos -> this.exit = blockPos); + } + } } private void doEffects(ServerLevel serverWorld, BlockPos center) { @@ -233,30 +198,20 @@ public class EternalRitual { moveX = Direction.SOUTH; moveY = Direction.EAST; } - for (Point pos : STRUCTURE_MAP) { + for (Point pos : PEDESTAL_POSITIONS) { BlockPos.MutableBlockPos p = center.mutable(); p.move(moveX, pos.x).move(moveY, pos.y); serverWorld.sendParticles( ParticleTypes.PORTAL, - p.getX() + 0.5, - p.getY() + 1.5, - p.getZ() + 0.5, + p.getX() + 0.5, p.getY() + 1.5, p.getZ() + 0.5, 20, - 0, - 0, - 0, - 1 + 0, 0, 0, 1 ); serverWorld.sendParticles( ParticleTypes.REVERSE_PORTAL, - p.getX() + 0.5, - p.getY() + 1.5, - p.getZ() + 0.5, + p.getX() + 0.5, p.getY() + 1.5, p.getZ() + 0.5, 20, - 0, - 0, - 0, - 0.3 + 0, 0, 0, 0.3 ); } serverWorld.playSound(null, center, SoundEvents.END_PORTAL_SPAWN, SoundSource.NEUTRAL, 16, 1); @@ -265,8 +220,8 @@ public class EternalRitual { private void activatePortal(Level world, BlockPos center, int portalId) { BlockPos framePos = center.below(); Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; - BlockState frame = FRAME.defaultBlockState().setValue(ACTIVE, true); - FRAME_MAP.forEach(point -> { + BlockState frame = PortalBuilder.FRAME.defaultBlockState().setValue(ACTIVE, true); + PortalBuilder.FRAME_POSITIONS.forEach(point -> { BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y); BlockState state = world.getBlockState(pos); if (state.hasProperty(ACTIVE) && !state.getValue(ACTIVE)) { @@ -279,63 +234,43 @@ public class EternalRitual { } }); Direction.Axis portalAxis = Direction.Axis.X == axis ? Direction.Axis.Z : Direction.Axis.X; - BlockState portal = PORTAL.defaultBlockState() - .setValue(EndPortalBlock.AXIS, portalAxis) - .setValue(EndPortalBlock.PORTAL, portalId); + BlockState portal = PortalBuilder.PORTAL.defaultBlockState() + .setValue(EndPortalBlock.AXIS, portalAxis) + .setValue(EndPortalBlock.PORTAL, portalId); ParticleOptions effect = new BlockParticleOption(ParticleTypes.BLOCK, portal); ServerLevel serverWorld = (ServerLevel) world; - PORTAL_MAP.forEach(point -> { + PortalBuilder.PORTAL_POSITIONS.forEach(point -> { BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y); - if (!world.getBlockState(pos).is(PORTAL)) { + if (!world.getBlockState(pos).is(PortalBuilder.PORTAL)) { world.setBlockAndUpdate(pos, portal); serverWorld.sendParticles( effect, - pos.getX() + 0.5, - pos.getY() + 0.5, - pos.getZ() + 0.5, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 10, - 0.5, - 0.5, - 0.5, - 0.1 + 0.5, 0.5, 0.5, 0.1 ); serverWorld.sendParticles( ParticleTypes.REVERSE_PORTAL, - pos.getX() + 0.5, - pos.getY() + 0.5, - pos.getZ() + 0.5, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 10, - 0.5, - 0.5, - 0.5, - 0.3 + 0.5, 0.5, 0.5, 0.3 ); } pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); - if (!world.getBlockState(pos).is(PORTAL)) { + if (!world.getBlockState(pos).is(PortalBuilder.PORTAL)) { world.setBlockAndUpdate(pos, portal); serverWorld.sendParticles( effect, - pos.getX() + 0.5, - pos.getY() + 0.5, - pos.getZ() + 0.5, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 10, - 0.5, - 0.5, - 0.5, - 0.1 + 0.5, 0.5, 0.5, 0.1 ); serverWorld.sendParticles( ParticleTypes.REVERSE_PORTAL, - pos.getX() + 0.5, - pos.getY() + 0.5, - pos.getZ() + 0.5, + pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 10, - 0.5, - 0.5, - 0.5, - 0.3 + 0.5, 0.5, 0.5, 0.3 ); } }); @@ -350,146 +285,31 @@ public class EternalRitual { private void removePortal(Level world, BlockPos center) { BlockPos framePos = center.below(); Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; - FRAME_MAP.forEach(point -> { + PortalBuilder.FRAME_POSITIONS.forEach(point -> { BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y); BlockState state = world.getBlockState(pos); - if (state.is(FRAME) && state.getValue(ACTIVE)) { + if (state.is(PortalBuilder.FRAME) && state.getValue(ACTIVE)) { world.setBlockAndUpdate(pos, state.setValue(ACTIVE, false)); } pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); state = world.getBlockState(pos); - if (state.is(FRAME) && state.getValue(ACTIVE)) { + if (state.is(PortalBuilder.FRAME) && state.getValue(ACTIVE)) { world.setBlockAndUpdate(pos, state.setValue(ACTIVE, false)); } }); - PORTAL_MAP.forEach(point -> { + PortalBuilder.PORTAL_POSITIONS.forEach(point -> { BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y); - if (world.getBlockState(pos).is(PORTAL)) { + if (world.getBlockState(pos).is(PortalBuilder.PORTAL)) { world.removeBlock(pos, false); } pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); - if (world.getBlockState(pos).is(PORTAL)) { + if (world.getBlockState(pos).is(PortalBuilder.PORTAL)) { world.removeBlock(pos, false); } }); this.active = false; } - @Nullable - private BlockPos findFrame(ServerLevel level, BlockPos.MutableBlockPos startPos) { - Optional found = EndPoiTypes.ETERNAL_PORTAL_INACTIVE.findPoiAround( - level, - startPos, - SEARCH_RADIUS >> 4 + 1, - level.getWorldBorder() - ); - if (found.isPresent()) { - if (checkFrame(level, found.get())) - return found.get(); - } - // List foundPos = findAllBlockPos( -// level, -// startPos, -// (SEARCH_RADIUS >> 4) + 1, -// FRAME, -// blockState -> blockState.is(FRAME) && !blockState.getValue(ACTIVE) -// ); -// for (BlockPos.MutableBlockPos testPos : foundPos) { -// if (checkFrame(level, testPos)) { -// return testPos; -// } -// } - return null; - } - - private BlockPos findPortalPos(int portalId) { - MinecraftServer server = world.getServer(); - ServerLevel targetWorld = (ServerLevel) getTargetWorld(portalId); - Registry registry = Objects.requireNonNull(server) - .registryAccess() - .registryOrThrow(Registries.DIMENSION_TYPE); - double multiplier = Objects.requireNonNull(registry.get(targetWorldId)).coordinateScale(); - BlockPos.MutableBlockPos basePos = center.mutable() - .set( - center.getX() / multiplier, - center.getY(), - center.getZ() / multiplier - ); - BlockPos framePos = findFrame(targetWorld, basePos.mutable()); - if (framePos != null) { - return framePos.above(); - } - Direction.Axis portalAxis = (Direction.Axis.X == axis) ? Direction.Axis.Z : Direction.Axis.X; - int worldCeil = targetWorld.getHeight() - 1; - - if (checkIsAreaValid(targetWorld, basePos, portalAxis)) { - generatePortal(targetWorld, basePos, portalAxis, portalId); - return basePos.immutable(); - } else { - Direction direction = Direction.EAST; - BlockPos.MutableBlockPos checkPos = basePos.mutable(); - int radius = (int) ((SEARCH_RADIUS / multiplier) + 1); - //make sure chunks are properly loaded for faster searches - PoiManager poiManager = targetWorld.getPoiManager(); - poiManager.ensureLoadedAndValid(world, checkPos, radius >> 4); - - for (int step = 1; step < radius; step++) { - for (int i = 0; i < (step >> 1); i++) { - ChunkAccess chunk = targetWorld.getChunk(checkPos); - if (chunk != null) { - int surfaceY = chunk.getHeight( - Heightmap.Types.WORLD_SURFACE, - checkPos.getX() & 15, - checkPos.getZ() & 15 - ); - int motionY = chunk.getHeight( - Heightmap.Types.MOTION_BLOCKING, - checkPos.getX() & 15, - checkPos.getZ() & 15 - ); - int ceil = Math.min(Math.max(surfaceY, motionY) + 1, worldCeil); - if (ceil < 5) continue; - checkPos.setY(ceil); - while (checkPos.getY() >= 5) { - if (checkIsAreaValid(targetWorld, checkPos, portalAxis)) { - generatePortal(targetWorld, checkPos, portalAxis, portalId); - return checkPos.immutable(); - } - checkPos.move(Direction.DOWN); - } - } - checkPos.move(direction); - } - direction = direction.getClockWise(); - } - } - if (targetWorld.dimension() == Level.END) { - WorldBootstrap.getLastRegistryAccess() - .registryOrThrow(Registries.CONFIGURED_FEATURE) - .get(net.minecraft.data.worldgen.features.EndFeatures.END_ISLAND) - .place( - targetWorld, - targetWorld.getChunkSource().getGenerator(), - new LegacyRandomSource(basePos.asLong()), - basePos.below() - ); - } else if (targetWorld.dimension() == Level.OVERWORLD) { - basePos.setY(targetWorld.getChunk(basePos) - .getHeight(Heightmap.Types.WORLD_SURFACE, basePos.getX(), basePos.getZ()) + 1); - } - EndFeatures.BIOME_ISLAND - .getPlacedFeature() - .value() - .place( - targetWorld, - targetWorld.getChunkSource().getGenerator(), - new LegacyRandomSource(basePos.asLong()), - basePos.below() - ); - generatePortal(targetWorld, basePos, portalAxis, portalId); - return basePos.immutable(); - } - private Level getTargetWorld(int state) { if (world.dimension() == Level.END) { return EndPortals.getWorld(world.getServer(), state); @@ -497,36 +317,6 @@ public class EternalRitual { return Objects.requireNonNull(world.getServer()).getLevel(Level.END); } - private boolean checkIsAreaValid(Level world, BlockPos pos, Direction.Axis axis) { - if (pos.getY() >= world.getHeight() - 1) return false; - if (!isBaseValid(world, pos, axis)) return false; - return EternalRitual.checkArea(world, pos, axis); - } - - private boolean isBaseValid(Level world, BlockPos pos, Direction.Axis axis) { - boolean solid = true; - if (axis.equals(Direction.Axis.X)) { - pos = pos.below().offset(0, 0, -3); - for (int i = 0; i < 7; i++) { - BlockPos checkPos = pos.offset(0, 0, i); - BlockState state = world.getBlockState(checkPos); - solid &= validBlock(world, checkPos, state); - } - } else { - pos = pos.below().offset(-3, 0, 0); - for (int i = 0; i < 7; i++) { - BlockPos checkPos = pos.offset(i, 0, 0); - BlockState state = world.getBlockState(checkPos); - solid &= validBlock(world, checkPos, state); - } - } - return solid; - } - - private boolean validBlock(Level world, BlockPos pos, BlockState state) { - return state.isRedstoneConductor(world, pos) && state.isCollisionShapeFullBlock(world, pos); - } - public void configure(BlockPos initial) { BlockPos checkPos = initial.east(12); if (this.hasPedestal(checkPos)) { @@ -653,146 +443,50 @@ public class EternalRitual { return world.equals(ritual.world) && Objects.equals(center, ritual.center) && Objects.equals(exit, ritual.exit); } - public static void generatePortal(Level world, BlockPos center, Direction.Axis axis, int portalId) { - BlockPos framePos = center.below(); - Direction moveDir = Direction.Axis.X == axis ? Direction.EAST : Direction.NORTH; - BlockState frame = FRAME.defaultBlockState().setValue(ACTIVE, true); - FRAME_MAP.forEach(point -> { - BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y); - world.setBlockAndUpdate(pos, frame); - pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); - world.setBlockAndUpdate(pos, frame); - }); - BlockState portal = PORTAL.defaultBlockState() - .setValue(EndPortalBlock.AXIS, axis) - .setValue(EndPortalBlock.PORTAL, portalId); - PORTAL_MAP.forEach(point -> { - BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y); - world.setBlockAndUpdate(pos, portal); - pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); - world.setBlockAndUpdate(pos, portal); - }); - generateBase(world, framePos, moveDir); - } + public static EternalRitual findRitualForActivePortal(Level world, BlockPos startPos) { + BlockState state = world.getBlockState(startPos); - private static void generateBase(Level world, BlockPos center, Direction moveX) { - BlockState base = BASE.defaultBlockState(); - Direction moveY = moveX.getClockWise(); - BASE_MAP.forEach(point -> { - BlockPos pos = center.mutable().move(moveX, point.x).move(moveY, point.y); - world.setBlockAndUpdate(pos, base); - pos = center.mutable().move(moveX, -point.x).move(moveY, point.y); - world.setBlockAndUpdate(pos, base); - pos = center.mutable().move(moveX, point.x).move(moveY, -point.y); - world.setBlockAndUpdate(pos, base); - pos = center.mutable().move(moveX, -point.x).move(moveY, -point.y); - world.setBlockAndUpdate(pos, base); - }); - } - - public static boolean checkArea(Level world, BlockPos center, Direction.Axis axis) { - Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; - for (BlockPos checkPos : BlockPos.betweenClosed( - center.relative(moveDir.getClockWise()), - center.relative(moveDir.getCounterClockWise()) - )) { - for (Point point : PORTAL_MAP) { - BlockPos pos = checkPos.mutable().move(moveDir, point.x).move(Direction.UP, point.y); - BlockState state = world.getBlockState(pos); - if (isStateInvalid(state)) return false; - pos = checkPos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); - state = world.getBlockState(pos); - if (isStateInvalid(state)) return false; + if (isActivePortalBlock(state)) { + final var direction = state.getValue(NetherPortalBlock.AXIS) == Direction.Axis.X + ? Direction.EAST + : Direction.NORTH; + while (world.getBlockState(startPos.below()).is(EndBlocks.END_PORTAL_BLOCK)) { + startPos = startPos.below(); } - } - return true; - } - private static boolean isStateInvalid(BlockState state) { - if (!state.getFluidState().isEmpty()) return true; - return !BlocksHelper.replaceableOrPlant(state); - } + // X O O O O O X + // X O O O O O X + // X a c O c b X + // X a O b X + // X X X - /** - * @param world World for search - * @param checkPos Start search position - * @param radius Search radius - * @param searchBlock Target block - * @param condition Predicate for test block states in the chunk section - * @return Position of the first found block or null. - */ - @Nullable - public static BlockPos.MutableBlockPos findBlockPos( - Level world, - BlockPos.MutableBlockPos checkPos, - int radius, - Block searchBlock, - Predicate condition - ) { - if (world instanceof ServerLevel server) { - BlockPos.MutableBlockPos altCheckPos = new BlockPos.MutableBlockPos( - checkPos.getX(), - checkPos.getY(), - checkPos.getZ() - ); - var pos = findClosestPoi(server, altCheckPos); - if (pos != null) { - checkPos = pos.mutable(); + + if (world.getBlockState(startPos.relative(direction, -1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL)) + startPos = startPos.relative(direction, 1); //pos (a) + else if (world.getBlockState(startPos.relative(direction, 1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL)) + startPos = startPos.relative(direction, -1); //pos (b) + + if (!world.getBlockState(startPos.below()).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL)) + startPos = startPos.below(); //pos (c) + + if (world.getBlockState(startPos.relative(direction, -1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL)) + startPos = startPos.relative(direction, 1); //pos (a) + else if (world.getBlockState(startPos.relative(direction, 1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL)) + startPos = startPos.relative(direction, -1); //pos (b) + + startPos = startPos.relative(direction.getClockWise(), 6); + state = world.getBlockState(startPos); + if (state.is(EndBlocks.ETERNAL_PEDESTAL) && world.getBlockEntity(startPos) instanceof EternalPedestalEntity pedestal) { + return pedestal.getRitual(); } + return null; } - Direction moveDirection = Direction.EAST; - for (int step = 1; step < radius; step++) { - for (int i = 0; i < (step >> 1); i++) { - ChunkAccess chunk = world.getChunk(checkPos); - if (!(chunk instanceof LevelChunk) || ((LevelChunk) chunk).isEmpty()) continue; - final LevelHeightAccessor levelHeightAccessor = chunk.getHeightAccessorForGeneration(); - LevelChunkSection section; - for (int k = levelHeightAccessor.getMinSection(); k < levelHeightAccessor.getMaxSection(); ++k) { - section = chunk.getSection(chunk.getSectionIndexFromSectionY(k)); - - if (section == null || !section.getStates().maybeHas(condition)) continue; - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - BlockState checkState = section.getBlockState(x, y, z); - if (checkState.is(searchBlock)) { - int worldX = (chunk.getPos().x << 4) + x; - int worldY = QuartPos.fromSection(k) + y; - int worldZ = (chunk.getPos().z << 4) + z; - checkPos.set(worldX, worldY, worldZ); - return checkPos; - } - } - } - } - } - System.out.print(checkPos + " -> " + moveDirection + " -> "); - checkPos.move(moveDirection, 16); - System.out.println(checkPos); - } - moveDirection = moveDirection.getClockWise(); - } return null; } - /** - * @param world World for search - * @param checkPos Start search position - * @return List of positions of the all found blocks or empty list. - */ - public static BlockPos findClosestPoi( - ServerLevel world, - BlockPos.MutableBlockPos checkPos - ) { - //make sure chunks are properly loaded for faster searches - PoiManager poiManager = world.getPoiManager(); - poiManager.ensureLoadedAndValid(world, checkPos, 128); - return EndPoiTypes.ETERNAL_PORTAL_ACTIVE.findClosest(world, checkPos, 128) - .orElse(null); - } - - public static int calculateSearchSteps(int chunkRadius) { - return chunkRadius * 4 - 1; + private static boolean isActivePortalBlock(BlockState state) { + return state.is(EndBlocks.END_PORTAL_BLOCK) + && state.hasProperty(NetherPortalBlock.AXIS); } } diff --git a/src/main/resources/betterend.mixins.common.json b/src/main/resources/betterend.mixins.common.json index 813af763..fe4fd534 100644 --- a/src/main/resources/betterend.mixins.common.json +++ b/src/main/resources/betterend.mixins.common.json @@ -28,7 +28,8 @@ "ServerPlayerMixin", "SlimeMixin", "SpikeFeatureMixin", - "WorldGenRegionMixin" + "WorldGenRegionMixin", + "portal.EntityMixin" ], "injectors": { "defaultRequire": 1