package nova.microblock.operation;
import nova.core.block.Block;
import nova.core.network.NetworkTarget;
import nova.core.util.math.Vector3DUtil;
import nova.core.util.shape.Cuboid;
import nova.core.world.World;
import nova.microblock.NovaMicroblock;
import nova.microblock.common.BlockContainer;
import nova.microblock.micro.Microblock;
import nova.microblock.micro.MicroblockContainer;
import nova.microblock.multi.Multiblock;
import nova.microblock.multi.MultiblockContainer;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* Handles mciroblock set operations.
*
* @author Calclavia
*/
public class ContainerPlace extends ContainerOperation {
//Positions that the operation handled.
public final Set<Vector3D> handledPositions = new HashSet<>();
private final NovaMicroblock.MicroblockInjectFactory injectFactory;
private final Block newBlock;
private final Optional<Vector3D> localPos;
/**
* Create a microblock operation handler
*
* @param world The world
* @param injectFactory The factory to injectToContainer
* @param globalPos The world position to handle wth block
* @param evt The block place event
*/
public ContainerPlace(World world, NovaMicroblock.MicroblockInjectFactory injectFactory, Vector3D globalPos, Block.PlaceEvent evt) {
super(world, globalPos);
this.injectFactory = injectFactory;
this.newBlock = injectFactory.containedFactory.build();
this.localPos = newBlock.components.get(Microblock.class).onPlace.apply(evt);
}
public ContainerPlace(World world, NovaMicroblock.MicroblockInjectFactory injectFactory, Vector3D globalPos) {
super(world, globalPos);
this.injectFactory = injectFactory;
this.newBlock = injectFactory.containedFactory.build();
this.localPos = Optional.empty();
}
//TODO: What happens when we try to set it to the void? Fail/remove all the blocks.
@Override
public boolean operate() {
if (NetworkTarget.Side.get().isClient()) {
return true;
}
Optional<Block> opContainer = getOrSetContainer(globalPos);
if (opContainer.isPresent()) {
Block container = opContainer.get();
//Add transform component
//newBlock.components.add(container.transform());
if (newBlock.components.has(Microblock.class)) {
assert localPos.isPresent();
//This is a microblock
//Attach microblock container to the container
MicroblockContainer microblockContainer = container.components.getOrAdd(new MicroblockContainer(container));
if (newBlock.components.has(Multiblock.class)) {
/**
* Generate multiblock containers to wrap this microblock
*/
Multiblock multiblock = newBlock.components.get(Multiblock.class);
//A set of world block space that are being occupied
Set<Vector3D> blockSpace = multiblock.getOccupiedSpace(1)
.stream()
.collect(Collectors.toSet());
Set<Vector3D> occupiedSpace = multiblock.getOccupiedSpace(1f / microblockContainer.subdivision);
populateBlockSpace(blockSpace,
(relativeBlockVec, outerContainerBlock) -> {
MicroblockContainer outerContainer = outerContainerBlock.components.getOrAdd(new MicroblockContainer(outerContainerBlock));
//Create a new multiblock inner container that lives inside the microblock structure.
BlockContainer innerContainer = new BlockContainer();
innerContainer.components.add(new MultiblockContainer(innerContainer, newBlock));
innerContainer.components.add(new Microblock(innerContainer)).setOnPlace(blockPlaceEvent -> localPos);
//Add transform component
innerContainer.components.add(outerContainerBlock.transform());
Set<Vector3D> localPositions = occupiedSpace.stream()
.map(vec -> vec.subtract(relativeBlockVec)) //Maps positions relative to its own block space
.filter(vec -> new Cuboid(Vector3D.ZERO, Vector3DUtil.ONE).intersects(vec)) //Filters blocks relevant to the relativeBlockVec
.map(vec -> vec.scalarMultiply(microblockContainer.subdivision)) //Multiply all unit vectors by subdivision size, converting it to local vectors.
.collect(Collectors.toSet());
localPositions.forEach(vec -> {
if (!outerContainer.put(vec, innerContainer.components.get(Microblock.class))) {
fail = true;
}
});
}
);
newBlock.components.get(Microblock.class).position = localPos.get();
//TODO: Handle injection
return handleFail();
} else {
/**
* Build microblocks without multiblocks
*/
if (!microblockContainer.putNew(localPos.get(), newBlock.components.get(Microblock.class))) {
fail = true;
}
return handleFail();
}
} else if (newBlock.components.has(Multiblock.class)) {
Multiblock multiblock = newBlock.components.get(Multiblock.class);
//Build multiblock without microblocks
Set<Vector3D> blockSpace = multiblock.getOccupiedSpace(1)
.stream()
.collect(Collectors.toSet());
populateBlockSpace(blockSpace,
(relativeBlockVec, outerContainerBlock) -> {
//Creates the outer container block that will exist in the world.
outerContainerBlock.components.getOrAdd(new MultiblockContainer(outerContainerBlock, newBlock));
}
);
//TODO: Handle injection
return handleFail();
}
}
return false;
}
/**
* @return False if failed
*/
protected boolean handleFail() {
if (fail) {
cleanup();
}
return !fail;
}
protected void cleanup() {
handledPositions.forEach(vector -> world.removeBlock(vector));
}
/**
* Populates a block space
*
* @param blockSpace The set of block positions where we want to populate container blocks
* @param func The callback function called after a container block is set.
* @return A set of containers actually placed into the world.
*/
protected Set<Block> populateBlockSpace(Set<Vector3D> blockSpace, BiConsumer<Vector3D, Block> func) {
Set<Block> populated = new HashSet<>();
blockSpace.forEach(relativeBlockVec -> {
//Note: relativeBlockVec is relative to globalPos.
Optional<Block> opInnerContainer = getOrSetContainer(globalPos.add(relativeBlockVec));
if (opInnerContainer.isPresent()) {
//Creates the outer container block that will exist in the world.
Block outerContainerBlock = opInnerContainer.get();
func.accept(relativeBlockVec, outerContainerBlock);
populated.add(outerContainerBlock);
}
});
return populated;
}
/**
* Checks a position in the world and either gets or sets the position into {@link BlockContainer}
*
* @param pos The position to set the container
* @return The container block
*/
public Optional<Block> getOrSetContainer(Vector3D pos) {
Optional<Block> opCheckBlock = world.getBlock(pos);
if (opCheckBlock.isPresent()) {
Block checkBlock = opCheckBlock.get();
if (checkBlock.sameType(NovaMicroblock.instance.blocks.getAirBlock())) {
//It's air, so let's create a container
world.setBlock(pos, injectFactory);
handledPositions.add(pos);
return world.getBlock(pos);
} else if (checkBlock.getID().startsWith("blockContainer-")) {
//There's already a microblock there.
return Optional.of(checkBlock);
}
}
fail = true;
return Optional.empty();
}
}