BetterEnd/src/main/java/ru/betterend/rituals/EternalRitual.java
2021-07-20 00:39:58 +03:00

777 lines
25 KiB
Java

package ru.betterend.rituals;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
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.data.worldgen.Features;
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.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.item.Item;
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.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.material.Material;
import org.jetbrains.annotations.Nullable;
import ru.bclib.blocks.BlockProperties;
import ru.betterend.BetterEnd;
import ru.betterend.blocks.EndPortalBlock;
import ru.betterend.blocks.RunedFlavolite;
import ru.betterend.blocks.entities.EternalPedestalEntity;
import ru.betterend.registry.EndBlocks;
import ru.betterend.registry.EndFeatures;
import ru.betterend.registry.EndPortals;
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;
public class EternalRitual {
private final static Set<Point> STRUCTURE_MAP = Sets.newHashSet(
new Point(-4, -5),
new Point(-4, 5),
new Point(-6, 0),
new Point(4, -5),
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);
private Level world;
private Direction.Axis axis;
private ResourceLocation targetWorldId;
private BlockPos center;
private BlockPos exit;
private boolean active = false;
public EternalRitual(Level world) {
this.world = world;
}
public EternalRitual(Level world, BlockPos initial) {
this(world);
this.configure(initial);
}
public void setWorld(Level world) {
this.world = world;
}
@Nullable
public ResourceLocation getTargetWorldId() {
return targetWorldId;
}
private boolean isInvalid() {
return world == null || world.isClientSide() || center == null || axis == null;
}
public void checkStructure() {
if (isInvalid()) return;
Direction moveX, moveY;
if (Direction.Axis.X == axis) {
moveX = Direction.EAST;
moveY = Direction.NORTH;
}
else {
moveX = Direction.SOUTH;
moveY = Direction.EAST;
}
boolean valid = checkFrame(world, center.below());
Item item = null;
for (Point pos : STRUCTURE_MAP) {
BlockPos.MutableBlockPos checkPos = center.mutable();
checkPos.move(moveX, pos.x).move(moveY, pos.y);
valid &= isActive(checkPos);
if (valid) {
EternalPedestalEntity pedestal = (EternalPedestalEntity) world.getBlockEntity(checkPos);
if (pedestal != null) {
Item pItem = pedestal.getItem(0).getItem();
if (item == null) {
item = pItem;
}
else if (!item.equals(pItem)) {
valid = false;
}
}
}
}
if (valid && item != null) {
activatePortal(item);
}
}
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) {
BlockPos pos = framePos.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
BlockState state = world.getBlockState(pos);
valid &= state.getBlock() instanceof RunedFlavolite;
pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
state = world.getBlockState(pos);
valid &= state.getBlock() instanceof RunedFlavolite;
}
return valid;
}
public boolean isActive() {
return active;
}
private void activatePortal(Item keyItem) {
if (active) return;
ResourceLocation itemId = Registry.ITEM.getKey(keyItem);
int portalId = EndPortals.getPortalIdByItem(itemId);
Level targetWorld = getTargetWorld(portalId);
ResourceLocation worldId = targetWorld.dimension().location();
try {
if (exit == null) {
initPortal(worldId, portalId);
}
else {
if (!worldId.equals(targetWorldId)) {
initPortal(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);
}
activatePortal(targetWorld, exit, portalId);
}
activatePortal(world, center, portalId);
doEffects((ServerLevel) world, center);
active = true;
}
catch (Exception ex) {
BetterEnd.LOGGER.error("Create End portals error.", ex);
removePortal(targetWorld, exit);
removePortal(world, center);
active = false;
}
}
private void initPortal(ResourceLocation worldId, int portalId) {
targetWorldId = worldId;
exit = findPortalPos(portalId);
}
private void doEffects(ServerLevel serverWorld, BlockPos center) {
Direction moveX, moveY;
if (Direction.Axis.X == axis) {
moveX = Direction.EAST;
moveY = Direction.NORTH;
}
else {
moveX = Direction.SOUTH;
moveY = Direction.EAST;
}
for (Point pos : STRUCTURE_MAP) {
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,
20,
0,
0,
0,
1
);
serverWorld.sendParticles(
ParticleTypes.REVERSE_PORTAL,
p.getX() + 0.5,
p.getY() + 1.5,
p.getZ() + 0.5,
20,
0,
0,
0,
0.3
);
}
serverWorld.playSound(null, center, SoundEvents.END_PORTAL_SPAWN, SoundSource.NEUTRAL, 16, 1);
}
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 -> {
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)) {
world.setBlockAndUpdate(pos, frame);
}
pos = framePos.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
state = world.getBlockState(pos);
if (state.hasProperty(ACTIVE) && !state.getValue(ACTIVE)) {
world.setBlockAndUpdate(pos, frame);
}
});
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);
ParticleOptions effect = new BlockParticleOption(ParticleTypes.BLOCK, portal);
ServerLevel serverWorld = (ServerLevel) world;
PORTAL_MAP.forEach(point -> {
BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
if (!world.getBlockState(pos).is(PORTAL)) {
world.setBlockAndUpdate(pos, portal);
serverWorld.sendParticles(
effect,
pos.getX() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
10,
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,
10,
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)) {
world.setBlockAndUpdate(pos, portal);
serverWorld.sendParticles(
effect,
pos.getX() + 0.5,
pos.getY() + 0.5,
pos.getZ() + 0.5,
10,
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,
10,
0.5,
0.5,
0.5,
0.3
);
}
});
}
public void disablePortal(int state) {
if (!active || isInvalid()) return;
removePortal(getTargetWorld(state), exit);
removePortal(world, center);
}
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 -> {
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)) {
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)) {
world.setBlockAndUpdate(pos, state.setValue(ACTIVE, false));
}
});
PORTAL_MAP.forEach(point -> {
BlockPos pos = center.mutable().move(moveDir, point.x).move(Direction.UP, point.y);
if (world.getBlockState(pos).is(PORTAL)) {
world.removeBlock(pos, false);
}
pos = center.mutable().move(moveDir, -point.x).move(Direction.UP, point.y);
if (world.getBlockState(pos).is(PORTAL)) {
world.removeBlock(pos, false);
}
});
this.active = false;
}
@Nullable
private BlockPos findFrame(Level world, BlockPos.MutableBlockPos startPos) {
List<BlockPos.MutableBlockPos> foundPos = findAllBlockPos(
world,
startPos,
(SEARCH_RADIUS >> 4) + 1,
FRAME,
blockState -> blockState.is(FRAME) && !blockState.getValue(ACTIVE)
);
for (BlockPos.MutableBlockPos testPos : foundPos) {
if (checkFrame(world, 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(Registry.DIMENSION_TYPE_REGISTRY);
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);
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) {
Features.END_ISLAND.place(
targetWorld,
targetWorld.getChunkSource().getGenerator(),
new Random(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.getFeatureConfigured()
.place(targetWorld,
targetWorld.getChunkSource().getGenerator(),
new Random(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);
}
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)) {
this.axis = Direction.Axis.X;
this.center = initial.east(6);
return;
}
checkPos = initial.west(12);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.X;
this.center = initial.west(6);
return;
}
checkPos = initial.south(12);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.Z;
this.center = initial.south(6);
return;
}
checkPos = initial.north(12);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.Z;
this.center = initial.north(6);
return;
}
checkPos = initial.north(10);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.X;
checkPos = checkPos.east(8);
if (this.hasPedestal(checkPos)) {
this.center = initial.north(5).east(4);
}
else {
this.center = initial.north(5).west(4);
}
return;
}
checkPos = initial.south(10);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.X;
checkPos = checkPos.east(8);
if (this.hasPedestal(checkPos)) {
this.center = initial.south(5).east(4);
}
else {
this.center = initial.south(5).west(4);
}
return;
}
checkPos = initial.east(10);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.Z;
checkPos = checkPos.south(8);
if (this.hasPedestal(checkPos)) {
this.center = initial.east(5).south(4);
}
else {
this.center = initial.east(5).north(4);
}
return;
}
checkPos = initial.west(10);
if (this.hasPedestal(checkPos)) {
this.axis = Direction.Axis.Z;
checkPos = checkPos.south(8);
if (this.hasPedestal(checkPos)) {
this.center = initial.west(5).south(4);
}
else {
this.center = initial.west(5).north(4);
}
}
}
private boolean hasPedestal(BlockPos pos) {
return world.getBlockState(pos).is(PEDESTAL);
}
private boolean isActive(BlockPos pos) {
BlockState state = world.getBlockState(pos);
if (state.is(PEDESTAL)) {
EternalPedestalEntity pedestal = (EternalPedestalEntity) world.getBlockEntity(pos);
if (pedestal != null) {
if (!pedestal.hasRitual()) {
pedestal.linkRitual(this);
}
else {
EternalRitual ritual = pedestal.getRitual();
if (!ritual.equals(this)) {
pedestal.linkRitual(this);
}
}
}
return state.getValue(ACTIVE);
}
return false;
}
public CompoundTag toTag(CompoundTag tag) {
tag.put("center", NbtUtils.writeBlockPos(center));
tag.putString("axis", axis.getName());
tag.putBoolean("active", active);
if (targetWorldId != null) {
tag.putString("key_item", targetWorldId.toString());
}
if (exit != null) {
tag.put("exit", NbtUtils.writeBlockPos(exit));
}
return tag;
}
public void fromTag(CompoundTag tag) {
axis = Direction.Axis.byName(tag.getString("axis"));
center = NbtUtils.readBlockPos(tag.getCompound("center"));
active = tag.getBoolean("active");
if (tag.contains("exit")) {
exit = NbtUtils.readBlockPos(tag.getCompound("exit"));
}
if (tag.contains("key_item")) {
targetWorldId = new ResourceLocation(tag.getString("key_item"));
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EternalRitual ritual = (EternalRitual) o;
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);
}
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;
}
}
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.MutableBlockPos findBlockPos(Level world, BlockPos.MutableBlockPos checkPos, int radius, Block searchBlock, Predicate<BlockState> condition) {
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;
for (LevelChunkSection section : chunk.getSections()) {
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 = section.bottomBlockY() + y;
int worldZ = (chunk.getPos().z << 4) + z;
checkPos.set(worldX, worldY, worldZ);
return checkPos;
}
}
}
}
}
checkPos.move(moveDirection, 16);
}
moveDirection = moveDirection.getClockWise();
}
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<BlockPos.MutableBlockPos> findAllBlockPos(Level world, BlockPos.MutableBlockPos checkPos, int radius, Block searchBlock, Predicate<BlockState> condition) {
List<BlockPos.MutableBlockPos> posFound = Lists.newArrayList();
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;
for (LevelChunkSection section : chunk.getSections()) {
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 = section.bottomBlockY() + y;
int worldZ = (chunk.getPos().z << 4) + z;
checkPos.set(worldX, worldY, worldZ);
posFound.add(checkPos.mutable());
}
}
}
}
}
checkPos.move(moveDirection, 16);
}
moveDirection = moveDirection.getClockWise();
}
return posFound;
}
public static int calculateSearchSteps(int radius) {
return radius * 4 - 1;
}
}