package slimeknights.tconstruct.smeltery.multiblock;
import com.google.common.collect.Lists;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import java.util.List;
public abstract class MultiblockCuboid extends MultiblockDetection {
// if the multiblock requires a floor/ceiling
public final boolean hasCeiling;
public final boolean hasFloor;
public final boolean hasFrame; // whether the frame needs to be present
public MultiblockCuboid(boolean hasFloor, boolean hasFrame, boolean hasCeiling) {
this.hasCeiling = hasCeiling;
this.hasFloor = hasFloor;
this.hasFrame = hasFrame;
}
/**
* Detects a cuboid multiblock
*
* @param world The world.
* @param center A position inside the multiblock at the height of the master block
* @param limit Maximum INNER size of the multiblock.
* @return Info about the multiblock or null if none was found
*/
@Override
public MultiblockStructure detectMultiblock(World world, BlockPos center, int limit) {
// list of blocks that are part of the multiblock
List<BlockPos> subBlocks = Lists.newArrayList();
// move as low as possible
int masterY = center.getY();
center = getOuterPos(world, center, EnumFacing.DOWN, limit + 1).up();
// distances to the edges including the outer blocks
int edges[] = new int[4];
// order: south/west/north/east
for(EnumFacing direction : EnumFacing.HORIZONTALS) {
// move to wall
BlockPos pos = getOuterPos(world, center, direction, limit);
edges[direction.getHorizontalIndex()] = (pos.getX() - center.getX()) + (pos.getZ() - center.getZ());
}
// walls too far away?
int xd = (edges[EnumFacing.SOUTH.getHorizontalIndex()] - edges[EnumFacing.NORTH.getHorizontalIndex()]) - 1;
int zd = (edges[EnumFacing.EAST.getHorizontalIndex()] - edges[EnumFacing.WEST.getHorizontalIndex()]) - 1;
if(xd > limit ||
zd > limit) {
return null;
}
// check the floor (frame check done inside)
if(hasFloor) {
if(!detectFloor(world, center.down(), edges, subBlocks)) {
return null;
}
}
// go up layer for layer (again, frame check done inside)
int height = 0;
for(; height + center.getY() < world.getHeight(); height++) {
if(!detectLayer(world, center.up(height), height, edges, subBlocks)) {
break;
}
}
// no walls?
if(height < 1 + masterY - center.getY()) {
return null;
}
// detect ceiling (yup. frame check done inside.)
if(hasCeiling) {
// move as high as possible
// basically "height" failed above meaning thats the number we are left with
// and assuming its a valid structure, it failed because its a ceiling (if another reason, the ceiling check will fail)
if(!detectCeiling(world, center.up(height), edges, subBlocks)) {
return null;
}
}
return new MultiblockStructure(xd, height, zd, subBlocks);
}
/* Valid Blocks */
public boolean isFloorBlock(World world, BlockPos pos) {
return isValidBlock(world, pos);
}
public boolean isCeilingBlock(World world, BlockPos pos) {
return isValidBlock(world, pos);
}
public boolean isFrameBlock(World world, BlockPos pos, EnumFrameType type) {
return isValidBlock(world, pos);
}
public boolean isWallBlock(World world, BlockPos pos) {
return isValidBlock(world, pos);
}
/* Detecting the outer shapes */
protected boolean detectFloor(World world, BlockPos center, int[] edges, List<BlockPos> subBlocks) {
return detectPlaneXZ(world, center, edges, false, subBlocks);
}
private boolean detectCeiling(World world, BlockPos center, int[] edges, List<BlockPos> subBlocks) {
return detectPlaneXZ(world, center, edges, true, subBlocks);
}
protected boolean detectPlaneXZ(World world, BlockPos center, int[] edges, boolean ceiling, List<BlockPos> subBlocks) {
BlockPos from = center.add(edges[1], 0, edges[2]);
BlockPos to = center.add(edges[3], 0, edges[0]);
List<BlockPos> candidates = Lists.newArrayList();
// validate frame first
if(hasFrame) {
// calculate blocks
List<BlockPos> frame = Lists.newArrayList();
// x direction
for(int x = 0; x <= to.getX() - from.getX(); x++) {
frame.add(from.add(x, 0, 0));
frame.add(to.add(-x, 0, 0));
}
// z direction. don't doublecheck corners
for(int z = 1; z < to.getZ() - from.getZ(); z++) {
frame.add(from.add(0, 0, z));
frame.add(to.add(0, 0, -z));
}
// check the blocks
for(BlockPos pos : frame) {
if(!isFrameBlock(world, pos, ceiling ? EnumFrameType.CEILING : EnumFrameType.FLOOR)) {
return false;
}
candidates.add(pos);
}
}
// validate inside of the floor
from = from.add(1, 0, 1);
to = to.add(-1, 0, -1);
for(BlockPos z = from; z.getZ() <= to.getZ(); z = z.add(0, 0, 1)) {
for(BlockPos x = z; x.getX() <= to.getX(); x = x.add(1, 0, 0)) {
if(ceiling && !isCeilingBlock(world, x)) {
return false;
}
else if(!ceiling && !isFloorBlock(world, x)) {
return false;
}
candidates.add(x);
}
}
subBlocks.addAll(candidates);
return true;
}
protected boolean detectLayer(World world, BlockPos center, int layer, int[] edges, List<BlockPos> subBlocks) {
BlockPos from = center.add(edges[1], 0, edges[2]);
BlockPos to = center.add(edges[3], 0, edges[0]);
List<BlockPos> candidates = Lists.newArrayList();
// validate frame first
if(hasFrame) {
// calculate blocks
List<BlockPos> frame = Lists.newArrayList();
// we only have 4 corner blocks to check
frame.add(from);
frame.add(to);
frame.add(new BlockPos(to.getX(), from.getY(), from.getZ()));
frame.add(new BlockPos(from.getX(), from.getY(), to.getZ()));
// check the blocks
for(BlockPos pos : frame) {
if(!isFrameBlock(world, pos, EnumFrameType.WALL)) {
return false;
}
candidates.add(pos);
}
}
// validate the inside
List<BlockPos> blocks = Lists.newArrayList();
for(int x = edges[1] + 1; x < edges[3]; x++) {
for(int z = edges[2] + 1; z < edges[0]; z++) {
blocks.add(center.add(x, 0, z));
}
}
for(BlockPos pos : blocks) {
if(!isInnerBlock(world, pos)) {
return false;
}
if(!world.isAirBlock(pos)) {
candidates.add(pos);
}
}
// validate the 4 sides
blocks.clear();
for(int x = edges[1] + 1; x < edges[3]; x++) {
blocks.add(center.add(x, 0, edges[2]));
blocks.add(center.add(x, 0, edges[0]));
}
for(int z = edges[2] + 1; z < edges[0]; z++) {
blocks.add(center.add(edges[1], 0, z));
blocks.add(center.add(edges[3], 0, z));
}
for(BlockPos pos : blocks) {
if(!isWallBlock(world, pos)) {
return false;
}
candidates.add(pos);
}
subBlocks.addAll(candidates);
return true;
}
public static enum EnumFrameType {
FLOOR,
CEILING,
WALL;
}
}