use of forestry.api.multiblock.IMultiblockComponent in project ForestryMC by ForestryMC.
the class MultiblockControllerBase method attachBlock.
@Override
public void attachBlock(IMultiblockComponent part) {
BlockPos coord = part.getCoordinates();
if (!connectedParts.add(part)) {
Log.warning("[%s] Controller %s is double-adding part %d @ %s. This is unusual. " + "If you encounter odd behavior, please tear down the machine and rebuild it.", world.isRemote ? "CLIENT" : "SERVER", hashCode(), part.hashCode(), coord);
}
MultiblockLogic logic = (MultiblockLogic) part.getMultiblockLogic();
logic.setController(this);
this.onBlockAdded(part);
if (logic.hasMultiblockSaveData()) {
NBTTagCompound savedData = logic.getMultiblockSaveData();
onAttachedPartWithMultiblockData(part, savedData);
logic.onMultiblockDataAssimilated();
}
if (this.referenceCoord == null) {
referenceCoord = coord;
logic.becomeMultiblockSaveDelegate();
} else if (coord.compareTo(referenceCoord) < 0) {
TileUtil.actOnTile(world, referenceCoord, IMultiblockComponent.class, tile -> {
MultiblockLogic teLogic = (MultiblockLogic) tile.getMultiblockLogic();
teLogic.forfeitMultiblockSaveDelegate();
});
referenceCoord = coord;
logic.becomeMultiblockSaveDelegate();
} else {
logic.forfeitMultiblockSaveDelegate();
}
if (minimumCoord != null) {
if (coord.getX() < minimumCoord.getX()) {
minimumCoord = new BlockPos(coord.getX(), minimumCoord.getY(), minimumCoord.getZ());
}
if (coord.getY() < minimumCoord.getY()) {
minimumCoord = new BlockPos(minimumCoord.getX(), coord.getY(), minimumCoord.getZ());
}
if (coord.getZ() < minimumCoord.getZ()) {
minimumCoord = new BlockPos(minimumCoord.getX(), minimumCoord.getY(), coord.getZ());
}
}
if (maximumCoord != null) {
if (coord.getX() > maximumCoord.getX()) {
maximumCoord = new BlockPos(coord.getX(), maximumCoord.getY(), maximumCoord.getZ());
}
if (coord.getY() > maximumCoord.getY()) {
maximumCoord = new BlockPos(maximumCoord.getX(), coord.getY(), maximumCoord.getZ());
}
if (coord.getZ() > maximumCoord.getZ()) {
maximumCoord = new BlockPos(maximumCoord.getX(), maximumCoord.getY(), coord.getZ());
}
}
MultiblockRegistry.addDirtyController(world, this);
}
use of forestry.api.multiblock.IMultiblockComponent in project ForestryMC by ForestryMC.
the class MultiblockControllerBase method disassembleMachine.
/**
* Called when the machine needs to be disassembled.
* It is not longer "whole" and should not be functional, usually
* as a result of a block being removed.
* Calls onMachineBroken on all attached parts.
*/
private void disassembleMachine() {
this.assemblyState = AssemblyState.Disassembled;
for (IMultiblockComponent part : connectedParts) {
part.onMachineBroken();
}
onMachineDisassembled();
}
use of forestry.api.multiblock.IMultiblockComponent in project ForestryMC by ForestryMC.
the class MultiblockControllerBase method assimilate.
@Override
public void assimilate(IMultiblockControllerInternal other) {
BlockPos otherReferenceCoord = other.getReferenceCoord();
BlockPos referenceCoord = getReferenceCoord();
if (otherReferenceCoord != null && referenceCoord != null && referenceCoord.compareTo(otherReferenceCoord) >= 0) {
throw new IllegalArgumentException("The controller with the lowest minimum-coord value must consume the one with the higher coords");
}
Set<IMultiblockComponent> partsToAcquire = new HashSet<>(other.getComponents());
// releases all blocks and references gently so they can be incorporated into another multiblock
other._onAssimilated(this);
for (IMultiblockComponent acquiredPart : partsToAcquire) {
// By definition, none of these can be the minimum block.
if (isInvalid(acquiredPart)) {
continue;
}
connectedParts.add(acquiredPart);
MultiblockLogic logic = (MultiblockLogic) acquiredPart.getMultiblockLogic();
logic.setController(this);
this.onBlockAdded(acquiredPart);
}
this.onAssimilate(other);
other.onAssimilated(this);
}
use of forestry.api.multiblock.IMultiblockComponent in project ForestryMC by ForestryMC.
the class MultiblockUtil method getNeighboringParts.
/**
* Returns an array containing references to neighboring IMultiblockComponent tile entities.
* Primarily a utility method. Only works after tileentity construction.
* <p>
* This method is chunk-safe on the server; it will not query for parts in chunks that are unloaded.
* Note that no method is chunk-safe on the client, because ChunkProviderClient is stupid.
*
* @return An array of references to neighboring IMultiblockComponent tile entities.
*/
public static List<IMultiblockComponent> getNeighboringParts(World world, IMultiblockComponent part) {
BlockPos partCoord = part.getCoordinates();
List<BlockPos> neighbors = new ArrayList<>(EnumFacing.values().length);
for (EnumFacing facing : EnumFacing.values()) {
BlockPos neighborCoord = new BlockPos(partCoord);
neighborCoord = neighborCoord.offset(facing);
neighbors.add(neighborCoord);
}
List<IMultiblockComponent> neighborParts = new ArrayList<>();
IChunkProvider chunkProvider = world.getChunkProvider();
for (BlockPos neighbor : neighbors) {
if (chunkProvider.getLoadedChunk(neighbor.getX() >> 4, neighbor.getZ() >> 4) == null) {
// Chunk not loaded, skip it.
continue;
}
TileUtil.actOnTile(world, neighbor, IMultiblockComponent.class, neighborParts::add);
}
return neighborParts;
}
use of forestry.api.multiblock.IMultiblockComponent in project ForestryMC by ForestryMC.
the class MultiblockWorldRegistry method processMultiblockChanges.
/**
* Called prior to processing multiblock controllers. Do bookkeeping.
*/
public void processMultiblockChanges() {
IChunkProvider chunkProvider = world.getChunkProvider();
BlockPos coord;
// Merge pools - sets of adjacent machines which should be merged later on in processing
List<Set<IMultiblockControllerInternal>> mergePools = null;
if (!orphanedParts.isEmpty()) {
Set<IMultiblockComponent> orphansToProcess = null;
// It's possible to polyfill this, but the polyfill is too slow for comfort.
synchronized (orphanedPartsMutex) {
if (!orphanedParts.isEmpty()) {
orphansToProcess = orphanedParts;
orphanedParts = new HashSet<>();
}
}
if (orphansToProcess != null && !orphansToProcess.isEmpty()) {
Set<IMultiblockControllerInternal> compatibleControllers;
// These are blocks that exist in a valid chunk and require a controller
for (IMultiblockComponent orphan : orphansToProcess) {
coord = orphan.getCoordinates();
if (chunkProvider.getLoadedChunk(coord.getX() >> 4, coord.getZ() >> 4) == null) {
continue;
}
// This can occur on slow machines.
if (orphan instanceof TileEntity && ((TileEntity) orphan).isInvalid()) {
continue;
}
if (TileUtil.getTile(world, coord) != orphan) {
// This block has been replaced by another.
continue;
}
// THIS IS THE ONLY PLACE WHERE PARTS ATTACH TO MACHINES
// Try to attach to a neighbor's master controller
compatibleControllers = attachToNeighbors(orphan);
if (compatibleControllers.isEmpty()) {
// FOREVER ALONE! Create and register a new controller.
// THIS IS THE ONLY PLACE WHERE NEW CONTROLLERS ARE CREATED.
MultiblockLogic logic = (MultiblockLogic) orphan.getMultiblockLogic();
IMultiblockControllerInternal newController = logic.createNewController(world);
newController.attachBlock(orphan);
this.controllers.add(newController);
} else if (compatibleControllers.size() > 1) {
if (mergePools == null) {
mergePools = new ArrayList<>();
}
// THIS IS THE ONLY PLACE WHERE MERGES ARE DETECTED
// Multiple compatible controllers indicates an impending merge.
// Locate the appropriate merge pool(s)
List<Set<IMultiblockControllerInternal>> candidatePools = new ArrayList<>();
for (Set<IMultiblockControllerInternal> candidatePool : mergePools) {
if (!Collections.disjoint(candidatePool, compatibleControllers)) {
// They share at least one element, so that means they will all touch after the merge
candidatePools.add(candidatePool);
}
}
if (candidatePools.size() <= 0) {
// No pools nearby, create a new merge pool
mergePools.add(compatibleControllers);
} else if (candidatePools.size() == 1) {
// Only one pool nearby, simply add to that one
candidatePools.get(0).addAll(compatibleControllers);
} else {
// Multiple pools- merge into one, then add the compatible controllers
Set<IMultiblockControllerInternal> masterPool = candidatePools.get(0);
Set<IMultiblockControllerInternal> consumedPool;
for (int i = 1; i < candidatePools.size(); i++) {
consumedPool = candidatePools.get(i);
masterPool.addAll(consumedPool);
mergePools.remove(consumedPool);
}
masterPool.addAll(compatibleControllers);
}
}
}
}
}
if (mergePools != null && !mergePools.isEmpty()) {
// should voltron the fuck up.
for (Set<IMultiblockControllerInternal> mergePool : mergePools) {
// Search for the new master machine, which will take over all the blocks contained in the other machines
IMultiblockControllerInternal newMaster = null;
for (IMultiblockControllerInternal controller : mergePool) {
if (newMaster == null || controller.shouldConsume(newMaster)) {
newMaster = controller;
}
}
if (newMaster == null) {
Log.error("Multiblock system checked a merge pool of size %d, found no master candidates. This should never happen.", mergePool.size());
} else {
// Merge all the other machines into the master machine, then unregister them
addDirtyController(newMaster);
for (IMultiblockControllerInternal controller : mergePool) {
if (controller != newMaster) {
newMaster.assimilate(controller);
addDeadController(controller);
addDirtyController(newMaster);
}
}
}
}
}
// physically connected to their master.
if (!dirtyControllers.isEmpty()) {
for (IMultiblockControllerInternal controller : dirtyControllers) {
if (controller == null) {
continue;
}
// Tell the machine to check if any parts are disconnected.
// It should return a set of parts which are no longer connected.
// POSTCONDITION: The controller must have informed those parts that
// they are no longer connected to this machine.
Set<IMultiblockComponent> newlyDetachedParts = controller.checkForDisconnections();
if (!controller.isEmpty()) {
controller.recalculateMinMaxCoords();
controller.checkIfMachineIsWhole();
} else {
addDeadController(controller);
}
if (!newlyDetachedParts.isEmpty()) {
// Controller has shed some parts - add them to the detached list for delayed processing
detachedParts.addAll(newlyDetachedParts);
}
}
dirtyControllers.clear();
}
// Unregister dead controllers
if (!deadControllers.isEmpty()) {
for (IMultiblockControllerInternal controller : deadControllers) {
// Validate that they are empty/dead, then unregister them.
if (!controller.isEmpty()) {
Log.error("Found a non-empty controller. Forcing it to shed its blocks and die. This should never happen!");
detachedParts.addAll(controller.detachAllBlocks());
}
// THIS IS THE ONLY PLACE WHERE CONTROLLERS ARE UNREGISTERED.
this.controllers.remove(controller);
}
deadControllers.clear();
}
// list, and will be checked next tick to see if their chunk is still loaded.
for (IMultiblockComponent part : detachedParts) {
// Ensure parts know they're detached
MultiblockLogic logic = (MultiblockLogic) part.getMultiblockLogic();
logic.assertDetached(part);
}
addAllOrphanedPartsThreadsafe(detachedParts);
detachedParts.clear();
}
Aggregations