diff --git a/src/main/java/ru/betterend/blocks/EndPortalBlock.java b/src/main/java/ru/betterend/blocks/EndPortalBlock.java index 659725a6..8c39d171 100644 --- a/src/main/java/ru/betterend/blocks/EndPortalBlock.java +++ b/src/main/java/ru/betterend/blocks/EndPortalBlock.java @@ -27,10 +27,8 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction.Axis; import net.minecraft.util.math.Direction.AxisDirection; import net.minecraft.util.registry.Registry; -import net.minecraft.world.Heightmap; import net.minecraft.world.World; import net.minecraft.world.WorldAccess; -import net.minecraft.world.chunk.Chunk; import net.minecraft.world.dimension.DimensionType; import ru.betterend.client.render.ERenderLayer; import ru.betterend.interfaces.IColorProvider; @@ -38,14 +36,15 @@ import ru.betterend.interfaces.IRenderTypeable; import ru.betterend.interfaces.TeleportingEntity; import ru.betterend.registry.EndParticles; import ru.betterend.registry.EndPortals; +import ru.betterend.rituals.EternalRitual; public class EndPortalBlock extends NetherPortalBlock implements IRenderTypeable, IColorProvider { public static final IntProperty PORTAL = BlockProperties.PORTAL; - + public EndPortalBlock() { super(FabricBlockSettings.copyOf(Blocks.NETHER_PORTAL).resistance(Blocks.BEDROCK.getBlastResistance()).luminance(15)); } - + @Override protected void appendProperties(StateManager.Builder builder) { super.appendProperties(builder); @@ -82,30 +81,33 @@ public class EndPortalBlock extends NetherPortalBlock implements IRenderTypeable @Override public void onEntityCollision(BlockState state, World world, BlockPos pos, Entity entity) { - if (world instanceof ServerWorld && !entity.hasVehicle() && !entity.hasPassengers() && entity.canUsePortals()) { - if (entity.hasNetherPortalCooldown()) return; - entity.resetNetherPortalCooldown(); - ServerWorld currentWorld = (ServerWorld) world; - MinecraftServer server = currentWorld.getServer(); - ServerWorld targetWorld = EndPortals.getWorld(server, state.get(PORTAL)); - boolean isInEnd = currentWorld.getRegistryKey().equals(World.END); - ServerWorld destination = isInEnd ? targetWorld : server.getWorld(World.END); - BlockPos exitPos = findExitPos(currentWorld, destination, pos, entity); - if (exitPos == null) return; - if (entity instanceof ServerPlayerEntity) { - ServerPlayerEntity player = (ServerPlayerEntity) entity; - this.teleportPlayer(player, destination, exitPos); - } else { - TeleportingEntity teleEntity = (TeleportingEntity) entity; - teleEntity.beSetExitPos(exitPos); - Entity teleported = entity.moveToWorld(destination); - if (teleported != null) { - teleported.resetNetherPortalCooldown(); - } + if (world.isClient || !validate(entity)) return; + entity.resetNetherPortalCooldown(); + ServerWorld currentWorld = (ServerWorld) world; + MinecraftServer server = currentWorld.getServer(); + ServerWorld targetWorld = EndPortals.getWorld(server, state.get(PORTAL)); + boolean isInEnd = currentWorld.getRegistryKey().equals(World.END); + ServerWorld destination = isInEnd ? targetWorld : server.getWorld(World.END); + BlockPos exitPos = findExitPos(currentWorld, destination, pos, entity); + if (exitPos == null) return; + if (entity instanceof ServerPlayerEntity) { + ServerPlayerEntity player = (ServerPlayerEntity) entity; + teleportPlayer(player, destination, exitPos); + } else { + TeleportingEntity teleEntity = (TeleportingEntity) entity; + teleEntity.beSetExitPos(exitPos); + Entity teleported = entity.moveToWorld(destination); + if (teleported != null) { + teleported.resetNetherPortalCooldown(); } } } + private boolean validate(Entity entity) { + return !entity.hasVehicle() && !entity.hasPassengers() && + entity.canUsePortals() && !entity.hasNetherPortalCooldown(); + } + private void teleportPlayer(ServerPlayerEntity player, ServerWorld destination, BlockPos exitPos) { if (player.isCreative()) { player.teleport(destination, exitPos.getX() + 0.5, exitPos.getY(), exitPos.getZ() + 0.5, player.yaw, player.pitch); @@ -114,7 +116,6 @@ public class EndPortalBlock extends NetherPortalBlock implements IRenderTypeable teleEntity.beSetExitPos(exitPos); player.moveToWorld(destination); } - player.resetNetherPortalCooldown(); } @Override @@ -131,47 +132,31 @@ public class EndPortalBlock extends NetherPortalBlock implements IRenderTypeable double currentMultiplier = Objects.requireNonNull(registry.get(currentWorldId)).getCoordinateScale(); double multiplier = targetMultiplier > currentMultiplier ? 1.0 / targetMultiplier : currentMultiplier; BlockPos.Mutable basePos = currentPos.mutableCopy().set(currentPos.getX() * multiplier, currentPos.getY(), currentPos.getZ() * multiplier); - Direction direction = Direction.EAST; BlockPos.Mutable checkPos = basePos.mutableCopy(); - for (int step = 1; step <= 128; step++) { - for (int i = 0; i < (step >> 1); i++) { - Chunk chunk = targetWorld.getChunk(checkPos); - if (chunk != null) { - int surfaceY = chunk.sampleHeightmap(Heightmap.Type.WORLD_SURFACE, checkPos.getX() & 15, checkPos.getZ() & 15); - int motionY = chunk.sampleHeightmap(Heightmap.Type.MOTION_BLOCKING, checkPos.getX() & 15, checkPos.getZ() & 15); - int ceil = Math.max(surfaceY, motionY) + 1; - if (ceil < 5) continue; - checkPos.setY(ceil); - while (checkPos.getY() >= 5) { - BlockState state = targetWorld.getBlockState(checkPos); - if (state.isOf(this)) { - Axis axis = state.get(AXIS); - checkPos = findCenter(targetWorld, checkPos, axis); - - Direction frontDir = Direction.from(axis, AxisDirection.POSITIVE).rotateYClockwise(); - Direction entityDir = entity.getMovementDirection(); - if (entityDir.getAxis().isVertical()) { - entityDir = frontDir; - } - - if (frontDir != entityDir && frontDir.getOpposite() != entityDir) { - entity.applyRotation(BlockRotation.CLOCKWISE_90); - entityDir = entityDir.rotateYClockwise(); - } - return checkPos.offset(entityDir); - } - checkPos.move(Direction.DOWN); - } - } - checkPos.move(direction); + BlockState currentState = currentWorld.getBlockState(currentPos); + int radius = (EternalRitual.SEARCH_RADIUS >> 4) + 1; + checkPos = EternalRitual.findBlockPos(targetWorld, checkPos, radius, this, state -> state.isOf(this) && + state.get(PORTAL).equals(currentState.get(PORTAL))); + if (checkPos != null) { + BlockState checkState = targetWorld.getBlockState(checkPos); + Axis axis = checkState.get(AXIS); + checkPos = findCenter(targetWorld, checkPos, axis); + Direction frontDir = Direction.from(axis, AxisDirection.POSITIVE).rotateYClockwise(); + Direction entityDir = entity.getMovementDirection(); + if (entityDir.getAxis().isVertical()) { + entityDir = frontDir; } - direction = direction.rotateYClockwise(); + if (frontDir != entityDir && frontDir.getOpposite() != entityDir) { + entity.applyRotation(BlockRotation.CLOCKWISE_90); + entityDir = entityDir.rotateYClockwise(); + } + return checkPos.offset(entityDir); } return null; } private BlockPos.Mutable findCenter(World world, BlockPos.Mutable pos, Direction.Axis axis) { - return this.findCenter(world, pos, axis, 1); + return findCenter(world, pos, axis, 1); } private BlockPos.Mutable findCenter(World world, BlockPos.Mutable pos, Direction.Axis axis, int step) { diff --git a/src/main/java/ru/betterend/rituals/EternalRitual.java b/src/main/java/ru/betterend/rituals/EternalRitual.java index d0536bb0..76afba18 100644 --- a/src/main/java/ru/betterend/rituals/EternalRitual.java +++ b/src/main/java/ru/betterend/rituals/EternalRitual.java @@ -1,10 +1,16 @@ package ru.betterend.rituals; import java.awt.Point; +import java.util.List; import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.function.Predicate; +import com.google.common.collect.Lists; +import net.minecraft.util.BlockRotation; +import net.minecraft.world.chunk.ChunkSection; +import net.minecraft.world.chunk.WorldChunk; import org.jetbrains.annotations.Nullable; import com.google.common.collect.Sets; @@ -64,6 +70,8 @@ public class EternalRitual { 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 World world; private Direction.Axis axis; private Identifier targetWorldId; @@ -152,11 +160,14 @@ public class EternalRitual { int portalId = EndPortals.getPortalIdByItem(itemId); activatePortal(world, center, portalId); doEffects((ServerWorld) world, center); + World targetWorld = getTargetWorld(portalId); + Identifier worldId = targetWorld.getRegistryKey().getValue(); if (exit == null) { - exit = findPortalPos(portalId); + initPortal(worldId, portalId); } else { - World targetWorld = getTargetWorld(portalId); - if (!checkFrame(targetWorld, exit)) { + if (!worldId.equals(targetWorldId)) { + initPortal(worldId, portalId); + } else if (!checkFrame(targetWorld, exit.down())) { Direction.Axis portalAxis = (Direction.Axis.X == axis) ? Direction.Axis.Z : Direction.Axis.X; generatePortal(targetWorld, exit, portalAxis, portalId); } @@ -165,6 +176,11 @@ public class EternalRitual { this.active = true; } + private void initPortal(Identifier worldId, int portalId) { + targetWorldId = worldId; + exit = findPortalPos(portalId); + } + private void doEffects(ServerWorld serverWorld, BlockPos center) { Direction moveX, moveY; if (Direction.Axis.X == axis) { @@ -255,22 +271,38 @@ public class EternalRitual { this.active = false; } + @Nullable + private BlockPos findFrame(World world, BlockPos.Mutable startPos) { + List foundPos = findAllBlockPos(world, startPos, (SEARCH_RADIUS >> 4) + 1, FRAME, + blockState -> blockState.isOf(FRAME) && !blockState.get(ACTIVE)); + for(BlockPos.Mutable testPos : foundPos) { + if (checkFrame(world, testPos)) { + return testPos; + } + } + return null; + } + private BlockPos findPortalPos(int portalId) { - targetWorldId = EndPortals.getWorldId(portalId); MinecraftServer server = world.getServer(); ServerWorld targetWorld = (ServerWorld) getTargetWorld(portalId); Registry registry = Objects.requireNonNull(server).getRegistryManager().getDimensionTypes(); double multiplier = Objects.requireNonNull(registry.get(targetWorldId)).getCoordinateScale(); BlockPos.Mutable basePos = center.mutableCopy().set(center.getX() / multiplier, center.getY(), center.getZ() / multiplier); + BlockPos framePos = findFrame(targetWorld, basePos.mutableCopy()); + if (framePos != null) { + return framePos.up(); + } Direction.Axis portalAxis = (Direction.Axis.X == axis) ? Direction.Axis.Z : Direction.Axis.X; int worldCeil = targetWorld.getDimensionHeight() - 1; if (checkIsAreaValid(targetWorld, basePos, portalAxis)) { - EternalRitual.generatePortal(targetWorld, basePos, portalAxis, portalId); + generatePortal(targetWorld, basePos, portalAxis, portalId); return basePos.toImmutable(); } else { Direction direction = Direction.EAST; BlockPos.Mutable checkPos = basePos.mutableCopy(); - for (int step = 1; step <= 96; step++) { + int radius = (int) ((SEARCH_RADIUS / multiplier) + 1); + for (int step = 1; step < radius; step++) { for (int i = 0; i < (step >> 1); i++) { Chunk chunk = targetWorld.getChunk(checkPos); if (chunk != null) { @@ -340,62 +372,6 @@ public class EternalRitual { return state.isSolidBlock(world, pos) && state.isFullCube(world, pos); } - public static void generatePortal(World world, BlockPos center, Direction.Axis axis, int portalId) { - BlockPos framePos = center.down(); - Direction moveDir = Direction.Axis.X == axis ? Direction.EAST : Direction.NORTH; - BlockState frame = FRAME.getDefaultState().with(ACTIVE, true); - FRAME_MAP.forEach(point -> { - BlockPos pos = framePos.mutableCopy().move(moveDir, point.x).move(Direction.UP, point.y); - world.setBlockState(pos, frame); - pos = framePos.mutableCopy().move(moveDir, -point.x).move(Direction.UP, point.y); - world.setBlockState(pos, frame); - }); - BlockState portal = PORTAL.getDefaultState().with(EndPortalBlock.AXIS, axis).with(EndPortalBlock.PORTAL, portalId); - PORTAL_MAP.forEach(point -> { - BlockPos pos = center.mutableCopy().move(moveDir, point.x).move(Direction.UP, point.y); - world.setBlockState(pos, portal); - pos = center.mutableCopy().move(moveDir, -point.x).move(Direction.UP, point.y); - world.setBlockState(pos, portal); - }); - generateBase(world, framePos, moveDir); - } - - private static void generateBase(World world, BlockPos center, Direction moveX) { - BlockState base = BASE.getDefaultState(); - Direction moveY = moveX.rotateYClockwise(); - BASE_MAP.forEach(point -> { - BlockPos pos = center.mutableCopy().move(moveX, point.x).move(moveY, point.y); - world.setBlockState(pos, base); - pos = center.mutableCopy().move(moveX, -point.x).move(moveY, point.y); - world.setBlockState(pos, base); - pos = center.mutableCopy().move(moveX, point.x).move(moveY, -point.y); - world.setBlockState(pos, base); - pos = center.mutableCopy().move(moveX, -point.x).move(moveY, -point.y); - world.setBlockState(pos, base); - }); - } - - public static boolean checkArea(World world, BlockPos center, Direction.Axis axis) { - Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; - for (BlockPos checkPos : BlockPos.iterate(center.offset(moveDir.rotateYClockwise()), center.offset(moveDir.rotateYCounterclockwise()))) { - for (Point point : PORTAL_MAP) { - BlockPos pos = checkPos.mutableCopy().move(moveDir, point.x).move(Direction.UP, point.y); - BlockState state = world.getBlockState(pos); - if (isStateInvalid(state)) return false; - pos = checkPos.mutableCopy().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; - Material material = state.getMaterial(); - return !material.isReplaceable() && !material.equals(Material.PLANT); - } - public void configure(BlockPos initial) { BlockPos checkPos = initial.east(12); if (this.hasPedestal(checkPos)) { @@ -523,4 +499,144 @@ public class EternalRitual { Objects.equals(center, ritual.center) && Objects.equals(exit, ritual.exit); } + + public static void generatePortal(World world, BlockPos center, Direction.Axis axis, int portalId) { + BlockPos framePos = center.down(); + Direction moveDir = Direction.Axis.X == axis ? Direction.EAST : Direction.NORTH; + BlockState frame = FRAME.getDefaultState().with(ACTIVE, true); + FRAME_MAP.forEach(point -> { + BlockPos pos = framePos.mutableCopy().move(moveDir, point.x).move(Direction.UP, point.y); + world.setBlockState(pos, frame); + pos = framePos.mutableCopy().move(moveDir, -point.x).move(Direction.UP, point.y); + world.setBlockState(pos, frame); + }); + BlockState portal = PORTAL.getDefaultState().with(EndPortalBlock.AXIS, axis).with(EndPortalBlock.PORTAL, portalId); + PORTAL_MAP.forEach(point -> { + BlockPos pos = center.mutableCopy().move(moveDir, point.x).move(Direction.UP, point.y); + world.setBlockState(pos, portal); + pos = center.mutableCopy().move(moveDir, -point.x).move(Direction.UP, point.y); + world.setBlockState(pos, portal); + }); + generateBase(world, framePos, moveDir); + } + + private static void generateBase(World world, BlockPos center, Direction moveX) { + BlockState base = BASE.getDefaultState(); + Direction moveY = moveX.rotateYClockwise(); + BASE_MAP.forEach(point -> { + BlockPos pos = center.mutableCopy().move(moveX, point.x).move(moveY, point.y); + world.setBlockState(pos, base); + pos = center.mutableCopy().move(moveX, -point.x).move(moveY, point.y); + world.setBlockState(pos, base); + pos = center.mutableCopy().move(moveX, point.x).move(moveY, -point.y); + world.setBlockState(pos, base); + pos = center.mutableCopy().move(moveX, -point.x).move(moveY, -point.y); + world.setBlockState(pos, base); + }); + } + + public static boolean checkArea(World world, BlockPos center, Direction.Axis axis) { + Direction moveDir = Direction.Axis.X == axis ? Direction.NORTH : Direction.EAST; + for (BlockPos checkPos : BlockPos.iterate(center.offset(moveDir.rotateYClockwise()), center.offset(moveDir.rotateYCounterclockwise()))) { + for (Point point : PORTAL_MAP) { + BlockPos pos = checkPos.mutableCopy().move(moveDir, point.x).move(Direction.UP, point.y); + BlockState state = world.getBlockState(pos); + if (isStateInvalid(state)) return false; + pos = checkPos.mutableCopy().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; + Material material = state.getMaterial(); + return !material.isReplaceable() && !material.equals(Material.PLANT); + } + + /** + * @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.Mutable findBlockPos(World world, BlockPos.Mutable checkPos, int radius, Block searchBlock, Predicate condition) { + Direction moveDirection = Direction.EAST; + for (int step = 1; step < radius; step++) { + for (int i = 0; i < (step >> 1); i++) { + Chunk chunk = world.getChunk(checkPos); + if (!(chunk instanceof WorldChunk) || ((WorldChunk) chunk).isEmpty()) continue; + for (ChunkSection section : chunk.getSectionArray()) { + if (section == null || !section.getContainer().hasAny(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.isOf(searchBlock)) { + int worldX = (chunk.getPos().x << 4) + x; + int worldY = section.getYOffset() + y; + int worldZ = (chunk.getPos().z << 4) + z; + checkPos.set(worldX, worldY, worldZ); + return checkPos; + } + } + } + } + } + checkPos.move(moveDirection, 16); + } + moveDirection = moveDirection.rotateYClockwise(); + } + return null; + } + + /** + * @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 List of positions of the all found blocks or empty list. + */ + public static List findAllBlockPos(World world, BlockPos.Mutable checkPos, int radius, Block searchBlock, Predicate condition) { + List posFound = Lists.newArrayList(); + Direction moveDirection = Direction.EAST; + for (int step = 1; step < radius; step++) { + for (int i = 0; i < (step >> 1); i++) { + Chunk chunk = world.getChunk(checkPos); + if (!(chunk instanceof WorldChunk) || ((WorldChunk) chunk).isEmpty()) continue; + for (ChunkSection section : chunk.getSectionArray()) { + if (section == null || !section.getContainer().hasAny(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.isOf(searchBlock)) { + int worldX = (chunk.getPos().x << 4) + x; + int worldY = section.getYOffset() + y; + int worldZ = (chunk.getPos().z << 4) + z; + checkPos.set(worldX, worldY, worldZ); + posFound.add(checkPos.mutableCopy()); + } + } + } + } + } + checkPos.move(moveDirection, 16); + } + moveDirection = moveDirection.rotateYClockwise(); + } + return posFound; + } + + public static int calculateSearchSteps(int radius) { + return radius * 4 - 1; + } }