[Change] Improved Eternal Portal Code

This commit is contained in:
Frank 2023-06-08 23:05:28 +02:00
parent 7f8d8bef0c
commit 9f4999c966
10 changed files with 775 additions and 531 deletions

View file

@ -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<Entity> 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<DimensionType> 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));

View file

@ -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);
}
}

View file

@ -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))

View file

@ -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<PortalInfo> cir) {
// if (be_travelerState != null) {
// PortalInfo pi = be_travelerState.findDimensionEntryPoint(serverLevel);
// if (pi != null) cir.setReturnValue(pi);
// }
}
}

View file

@ -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<Point> 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<Point> 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<Point> 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<BlockUtil.FoundRectangle> findPortalAround(BlockPos blockPos, WorldBorder worldBorder) {
PoiManager poiManager = this.targetLevel.getPoiManager();
poiManager.ensureLoadedAndValid(this.targetLevel, blockPos, SPIRAL_SEARCH_RADIUS);
Optional<BlockPos> 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<BlockPos> 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;
}
}

View file

@ -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<BlockUtil.FoundRectangle> getExitPortal(
ServerLevel targetLevel,
BlockPos startingPos,
boolean bl,
WorldBorder worldBorder
) {
final PortalBuilder builder = new PortalBuilder(this.level(), targetLevel);
final Optional<BlockUtil.FoundRectangle> 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 + ']';
}
}

View file

@ -0,0 +1,10 @@
package org.betterx.betterend.portal;
/**
* Manages state for entities traveling through end portals.
*/
public interface TravelingEntity {
TravelerState be_getTravelerState();
}

View file

@ -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
);

View file

@ -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<Point> STRUCTURE_MAP = Sets.newHashSet(
private final static Set<Point> 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<Point> 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<Point> 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<Point> 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<BlockUtil.FoundRectangle> foundRectangle = builder.findPortalAround(
this.center,
worldBorder
);
if (!foundRectangle.isPresent()) {
Optional<BlockPos> 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<BlockPos> 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<BlockPos.MutableBlockPos> 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<DimensionType> 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<BlockState> 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);
}
}