[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.client.render.BCLRenderLayer;
import org.betterx.bclib.interfaces.CustomColorProvider; import org.betterx.bclib.interfaces.CustomColorProvider;
import org.betterx.bclib.interfaces.RenderLayerProvider; import org.betterx.bclib.interfaces.RenderLayerProvider;
import org.betterx.betterend.advancements.BECriteria; import org.betterx.betterend.portal.TravelingEntity;
import org.betterx.betterend.interfaces.TeleportingEntity;
import org.betterx.betterend.registry.EndParticles; import org.betterx.betterend.registry.EndParticles;
import org.betterx.betterend.registry.EndPortals; import org.betterx.betterend.registry.EndPortals;
import org.betterx.betterend.rituals.EternalRitual;
import net.minecraft.client.color.block.BlockColor; import net.minecraft.client.color.block.BlockColor;
import net.minecraft.client.color.item.ItemColor; import net.minecraft.client.color.item.ItemColor;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.BlockPos.MutableBlockPos;
import net.minecraft.core.Direction; 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.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource; 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.Block;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.NetherPortalBlock; 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.BlockState;
import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty; 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.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; 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 class EndPortalBlock extends NetherPortalBlock implements RenderLayerProvider, CustomColorProvider {
public static final IntegerProperty PORTAL = EndBlockProperties.PORTAL; public static final IntegerProperty PORTAL = EndBlockProperties.PORTAL;
@ -104,39 +89,16 @@ public class EndPortalBlock extends NetherPortalBlock implements RenderLayerProv
return state; return state;
} }
@Override @Override
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
if (world.isClientSide || !validate(entity)) return; if (validate(entity) && entity instanceof TravelingEntity te && te.be_getTravelerState() != null) {
entity.setPortalCooldown(); te.be_getTravelerState().handleInsidePortal(pos);
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);
} }
} }
private boolean validate(Entity entity) { private boolean validate(Entity entity) {
return !entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions() && !entity.isOnPortalCooldown(); return !entity.isPassenger() && !entity.isVehicle() && entity.canChangeDimensions();
} }
@Override @Override
@ -144,79 +106,6 @@ public class EndPortalBlock extends NetherPortalBlock implements RenderLayerProv
return BCLRenderLayer.TRANSLUCENT; 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 @Override
public BlockColor getProvider() { public BlockColor getProvider() {
return (state, world, pos, tintIndex) -> EndPortals.getColor(state.getValue(PORTAL)); return (state, world, pos, tintIndex) -> EndPortals.getColor(state.getValue(PORTAL));

View file

@ -38,11 +38,11 @@ public class EternalPedestal extends PedestalBlock {
} }
@Override @Override
public void checkRitual(Level world, Player player, BlockPos pos) { public void checkRitual(Level sourceLevel, Player player, BlockPos pos) {
BlockEntity blockEntity = world.getBlockEntity(pos); BlockEntity blockEntity = sourceLevel.getBlockEntity(pos);
if (blockEntity instanceof EternalPedestalEntity) { if (blockEntity instanceof EternalPedestalEntity) {
EternalPedestalEntity pedestal = (EternalPedestalEntity) blockEntity; EternalPedestalEntity pedestal = (EternalPedestalEntity) blockEntity;
BlockState updatedState = world.getBlockState(pos); BlockState updatedState = sourceLevel.getBlockState(pos);
if (pedestal.isEmpty()) { if (pedestal.isEmpty()) {
if (pedestal.hasRitual()) { if (pedestal.hasRitual()) {
EternalRitual ritual = pedestal.getRitual(); EternalRitual ritual = pedestal.getRitual();
@ -57,16 +57,19 @@ public class EternalPedestal extends PedestalBlock {
ritual.disablePortal(portalId); 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 { } else {
ItemStack itemStack = pedestal.getItem(0); ItemStack itemStack = pedestal.getItem(0);
ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemStack.getItem()); ResourceLocation id = BuiltInRegistries.ITEM.getKey(itemStack.getItem());
if (EndPortals.isAvailableItem(id)) { 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()) { if (pedestal.hasRitual()) {
pedestal.getRitual().checkStructure(player); pedestal.getRitual().checkStructure(player);
} else { } else {
EternalRitual ritual = new EternalRitual(world, pos); EternalRitual ritual = new EternalRitual(sourceLevel, pos);
ritual.checkStructure(player); ritual.checkStructure(player);
} }
} }

View file

@ -60,7 +60,11 @@ public class CommandRegistry {
.requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS))
.then(Commands.literal("locate_portal") .then(Commands.literal("locate_portal")
.requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) .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") .then(Commands.literal("tpnext")
.requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) .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.BetterEnd;
import org.betterx.betterend.blocks.RunedFlavolite; import org.betterx.betterend.blocks.RunedFlavolite;
import com.google.common.collect.ImmutableSet;
import java.util.Set; import java.util.Set;
public class EndPoiTypes { public class EndPoiTypes {
public static final BCLPoiType ETERNAL_PORTAL_INACTIVE = PoiManager.register( public static final BCLPoiType ETERNAL_PORTAL = PoiManager.register(
BetterEnd.makeID("eternal_portal_inactive"), BetterEnd.makeID("eternal_portal"),
Set.of(EndBlocks.FLAVOLITE_RUNED_ETERNAL.defaultBlockState().setValue(RunedFlavolite.ACTIVATED, false)), ImmutableSet.copyOf(EndBlocks.END_PORTAL_BLOCK.getStateDefinition().getPossibleStates()),
0, 1 0, 1
); );
public static final BCLPoiType ETERNAL_PORTAL_ACTIVE = PoiManager.register( public static final BCLPoiType ETERNAL_PORTAL_FRAME = PoiManager.register(
BetterEnd.makeID("eternal_portal_active"), BetterEnd.makeID("eternal_portal_frame"),
Set.of(EndBlocks.FLAVOLITE_RUNED_ETERNAL.defaultBlockState().setValue(RunedFlavolite.ACTIVATED, true)), Set.of(
EndBlocks.FLAVOLITE_RUNED_ETERNAL.defaultBlockState().setValue(RunedFlavolite.ACTIVATED, false)
),
0, 1 0, 1
); );

View file

@ -1,49 +1,37 @@
package org.betterx.betterend.rituals; package org.betterx.betterend.rituals;
import org.betterx.bclib.blocks.BlockProperties; import org.betterx.bclib.blocks.BlockProperties;
import org.betterx.bclib.util.BlocksHelper;
import org.betterx.betterend.BetterEnd; import org.betterx.betterend.BetterEnd;
import org.betterx.betterend.advancements.BECriteria; import org.betterx.betterend.advancements.BECriteria;
import org.betterx.betterend.blocks.EndPortalBlock; import org.betterx.betterend.blocks.EndPortalBlock;
import org.betterx.betterend.blocks.RunedFlavolite; import org.betterx.betterend.blocks.RunedFlavolite;
import org.betterx.betterend.blocks.entities.EternalPedestalEntity; import org.betterx.betterend.blocks.entities.EternalPedestalEntity;
import org.betterx.betterend.portal.PortalBuilder;
import org.betterx.betterend.registry.EndBlocks; 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.betterend.registry.EndPortals;
import org.betterx.worlds.together.world.event.WorldBootstrap;
import net.minecraft.BlockUtil;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; 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.BlockParticleOption;
import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource; 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.entity.player.Player;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level; 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.Block;
import net.minecraft.world.level.block.NetherPortalBlock;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty; import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.border.WorldBorder;
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 com.google.common.collect.Sets; import com.google.common.collect.Sets;
@ -51,11 +39,10 @@ import java.awt.*;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class EternalRitual { 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(-4, 5), new Point(-4, 5),
new Point(-6, 0), new Point(-6, 0),
@ -63,49 +50,9 @@ public class EternalRitual {
new Point(4, 5), new Point(4, 5),
new Point(6, 0) 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 PEDESTAL = EndBlocks.ETERNAL_PEDESTAL;
private final static Block FRAME = EndBlocks.FLAVOLITE_RUNED_ETERNAL; public final static BooleanProperty ACTIVE = BlockProperties.ACTIVE;
private final static Block PORTAL = EndBlocks.END_PORTAL_BLOCK;
private final static BooleanProperty ACTIVE = BlockProperties.ACTIVE;
public final static int SEARCH_RADIUS = calculateSearchSteps(48);
private Level world; private Level world;
private Direction.Axis axis; private Direction.Axis axis;
@ -148,7 +95,7 @@ public class EternalRitual {
} }
boolean valid = checkFrame(world, center.below()); boolean valid = checkFrame(world, center.below());
Item item = null; Item item = null;
for (Point pos : STRUCTURE_MAP) { for (Point pos : PEDESTAL_POSITIONS) {
BlockPos.MutableBlockPos checkPos = center.mutable(); BlockPos.MutableBlockPos checkPos = center.mutable();
checkPos.move(moveX, pos.x).move(moveY, pos.y); checkPos.move(moveX, pos.x).move(moveY, pos.y);
valid &= isActive(checkPos); valid &= isActive(checkPos);
@ -165,7 +112,7 @@ public class EternalRitual {
} }
} }
if (valid && item != null) { if (valid && item != null) {
activatePortal(item); activatePortal(player, item);
if (player instanceof ServerPlayer sp) { if (player instanceof ServerPlayer sp) {
BECriteria.PORTAL_ON.trigger(sp); BECriteria.PORTAL_ON.trigger(sp);
} }
@ -175,7 +122,7 @@ public class EternalRitual {
private boolean checkFrame(Level world, BlockPos framePos) { private boolean checkFrame(Level world, BlockPos framePos) {
Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST;
boolean valid = true; 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); BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
BlockState state = world.getBlockState(pos); BlockState state = world.getBlockState(pos);
valid &= state.getBlock() instanceof RunedFlavolite; valid &= state.getBlock() instanceof RunedFlavolite;
@ -190,7 +137,7 @@ public class EternalRitual {
return active; return active;
} }
private void activatePortal(Item keyItem) { private void activatePortal(Player player, Item keyItem) {
if (active) return; if (active) return;
ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(keyItem); ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(keyItem);
int portalId = EndPortals.getPortalIdByItem(itemId); int portalId = EndPortals.getPortalIdByItem(itemId);
@ -198,13 +145,13 @@ public class EternalRitual {
ResourceLocation worldId = targetWorld.dimension().location(); ResourceLocation worldId = targetWorld.dimension().location();
try { try {
if (exit == null) { if (exit == null) {
initPortal(worldId, portalId); initPortal(player, worldId, portalId);
} else { } else {
if (!worldId.equals(targetWorldId)) { if (!worldId.equals(targetWorldId)) {
initPortal(worldId, portalId); initPortal(player, worldId, portalId);
} else if (!checkFrame(targetWorld, exit.below())) { } else if (!checkFrame(targetWorld, exit.below())) {
Direction.Axis portalAxis = (Direction.Axis.X == axis) ? Direction.Axis.Z : Direction.Axis.X; 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); 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; 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) { private void doEffects(ServerLevel serverWorld, BlockPos center) {
@ -233,30 +198,20 @@ public class EternalRitual {
moveX = Direction.SOUTH; moveX = Direction.SOUTH;
moveY = Direction.EAST; moveY = Direction.EAST;
} }
for (Point pos : STRUCTURE_MAP) { for (Point pos : PEDESTAL_POSITIONS) {
BlockPos.MutableBlockPos p = center.mutable(); BlockPos.MutableBlockPos p = center.mutable();
p.move(moveX, pos.x).move(moveY, pos.y); p.move(moveX, pos.x).move(moveY, pos.y);
serverWorld.sendParticles( serverWorld.sendParticles(
ParticleTypes.PORTAL, ParticleTypes.PORTAL,
p.getX() + 0.5, p.getX() + 0.5, p.getY() + 1.5, p.getZ() + 0.5,
p.getY() + 1.5,
p.getZ() + 0.5,
20, 20,
0, 0, 0, 0, 1
0,
0,
1
); );
serverWorld.sendParticles( serverWorld.sendParticles(
ParticleTypes.REVERSE_PORTAL, ParticleTypes.REVERSE_PORTAL,
p.getX() + 0.5, p.getX() + 0.5, p.getY() + 1.5, p.getZ() + 0.5,
p.getY() + 1.5,
p.getZ() + 0.5,
20, 20,
0, 0, 0, 0, 0.3
0,
0,
0.3
); );
} }
serverWorld.playSound(null, center, SoundEvents.END_PORTAL_SPAWN, SoundSource.NEUTRAL, 16, 1); 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) { private void activatePortal(Level world, BlockPos center, int portalId) {
BlockPos framePos = center.below(); BlockPos framePos = center.below();
Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST;
BlockState frame = FRAME.defaultBlockState().setValue(ACTIVE, true); BlockState frame = PortalBuilder.FRAME.defaultBlockState().setValue(ACTIVE, true);
FRAME_MAP.forEach(point -> { PortalBuilder.FRAME_POSITIONS.forEach(point -> {
BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y); BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
BlockState state = world.getBlockState(pos); BlockState state = world.getBlockState(pos);
if (state.hasProperty(ACTIVE) && !state.getValue(ACTIVE)) { 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; Direction.Axis portalAxis = Direction.Axis.X == axis ? Direction.Axis.Z : Direction.Axis.X;
BlockState portal = PORTAL.defaultBlockState() BlockState portal = PortalBuilder.PORTAL.defaultBlockState()
.setValue(EndPortalBlock.AXIS, portalAxis) .setValue(EndPortalBlock.AXIS, portalAxis)
.setValue(EndPortalBlock.PORTAL, portalId); .setValue(EndPortalBlock.PORTAL, portalId);
ParticleOptions effect = new BlockParticleOption(ParticleTypes.BLOCK, portal); ParticleOptions effect = new BlockParticleOption(ParticleTypes.BLOCK, portal);
ServerLevel serverWorld = (ServerLevel) world; 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); 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); world.setBlockAndUpdate(pos, portal);
serverWorld.sendParticles( serverWorld.sendParticles(
effect, effect,
pos.getX() + 0.5, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
10, 10,
0.5, 0.5, 0.5, 0.5, 0.1
0.5,
0.5,
0.1
); );
serverWorld.sendParticles( serverWorld.sendParticles(
ParticleTypes.REVERSE_PORTAL, ParticleTypes.REVERSE_PORTAL,
pos.getX() + 0.5, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
10, 10,
0.5, 0.5, 0.5, 0.5, 0.3
0.5,
0.5,
0.3
); );
} }
pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); 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); world.setBlockAndUpdate(pos, portal);
serverWorld.sendParticles( serverWorld.sendParticles(
effect, effect,
pos.getX() + 0.5, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
10, 10,
0.5, 0.5, 0.5, 0.5, 0.1
0.5,
0.5,
0.1
); );
serverWorld.sendParticles( serverWorld.sendParticles(
ParticleTypes.REVERSE_PORTAL, ParticleTypes.REVERSE_PORTAL,
pos.getX() + 0.5, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
10, 10,
0.5, 0.5, 0.5, 0.5, 0.3
0.5,
0.5,
0.3
); );
} }
}); });
@ -350,146 +285,31 @@ public class EternalRitual {
private void removePortal(Level world, BlockPos center) { private void removePortal(Level world, BlockPos center) {
BlockPos framePos = center.below(); BlockPos framePos = center.below();
Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; 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); BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
BlockState state = world.getBlockState(pos); 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)); world.setBlockAndUpdate(pos, state.setValue(ACTIVE, false));
} }
pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
state = world.getBlockState(pos); 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)); 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); 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); world.removeBlock(pos, false);
} }
pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y); 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); world.removeBlock(pos, false);
} }
}); });
this.active = 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) { private Level getTargetWorld(int state) {
if (world.dimension() == Level.END) { if (world.dimension() == Level.END) {
return EndPortals.getWorld(world.getServer(), state); return EndPortals.getWorld(world.getServer(), state);
@ -497,36 +317,6 @@ public class EternalRitual {
return Objects.requireNonNull(world.getServer()).getLevel(Level.END); 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) { public void configure(BlockPos initial) {
BlockPos checkPos = initial.east(12); BlockPos checkPos = initial.east(12);
if (this.hasPedestal(checkPos)) { 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); 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) { public static EternalRitual findRitualForActivePortal(Level world, BlockPos startPos) {
BlockPos framePos = center.below(); BlockState state = world.getBlockState(startPos);
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);
}
private static void generateBase(Level world, BlockPos center, Direction moveX) { if (isActivePortalBlock(state)) {
BlockState base = BASE.defaultBlockState(); final var direction = state.getValue(NetherPortalBlock.AXIS) == Direction.Axis.X
Direction moveY = moveX.getClockWise(); ? Direction.EAST
BASE_MAP.forEach(point -> { : Direction.NORTH;
BlockPos pos = center.mutable().move(moveX, point.x).move(moveY, point.y); while (world.getBlockState(startPos.below()).is(EndBlocks.END_PORTAL_BLOCK)) {
world.setBlockAndUpdate(pos, base); startPos = startPos.below();
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;
} }
}
return true;
}
private static boolean isStateInvalid(BlockState state) { // X O O O O O X
if (!state.getFluidState().isEmpty()) return true; // X O O O O O X
return !BlocksHelper.replaceableOrPlant(state); // X a c O c b X
} // X a O b X
// X X X
/**
* @param world World for search if (world.getBlockState(startPos.relative(direction, -1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
* @param checkPos Start search position startPos = startPos.relative(direction, 1); //pos (a)
* @param radius Search radius else if (world.getBlockState(startPos.relative(direction, 1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
* @param searchBlock Target block startPos = startPos.relative(direction, -1); //pos (b)
* @param condition Predicate for test block states in the chunk section
* @return Position of the first found block or null. if (!world.getBlockState(startPos.below()).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
*/ startPos = startPos.below(); //pos (c)
@Nullable
public static BlockPos.MutableBlockPos findBlockPos( if (world.getBlockState(startPos.relative(direction, -1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
Level world, startPos = startPos.relative(direction, 1); //pos (a)
BlockPos.MutableBlockPos checkPos, else if (world.getBlockState(startPos.relative(direction, 1)).is(EndBlocks.FLAVOLITE_RUNED_ETERNAL))
int radius, startPos = startPos.relative(direction, -1); //pos (b)
Block searchBlock,
Predicate<BlockState> condition startPos = startPos.relative(direction.getClockWise(), 6);
) { state = world.getBlockState(startPos);
if (world instanceof ServerLevel server) { if (state.is(EndBlocks.ETERNAL_PEDESTAL) && world.getBlockEntity(startPos) instanceof EternalPedestalEntity pedestal) {
BlockPos.MutableBlockPos altCheckPos = new BlockPos.MutableBlockPos( return pedestal.getRitual();
checkPos.getX(),
checkPos.getY(),
checkPos.getZ()
);
var pos = findClosestPoi(server, altCheckPos);
if (pos != null) {
checkPos = pos.mutable();
} }
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; return null;
} }
/** private static boolean isActivePortalBlock(BlockState state) {
* @param world World for search return state.is(EndBlocks.END_PORTAL_BLOCK)
* @param checkPos Start search position && state.hasProperty(NetherPortalBlock.AXIS);
* @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;
} }
} }

View file

@ -28,7 +28,8 @@
"ServerPlayerMixin", "ServerPlayerMixin",
"SlimeMixin", "SlimeMixin",
"SpikeFeatureMixin", "SpikeFeatureMixin",
"WorldGenRegionMixin" "WorldGenRegionMixin",
"portal.EntityMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1