aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/api/multitileentity/multiblock
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/gregtech/api/multitileentity/multiblock')
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java111
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java1082
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java693
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java128
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/StackableModularController.java77
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/base/WallShareablePart.java100
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/BasicCasing.java7
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/CasingBehaviorBase.java10
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/FunctionalCasing.java29
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/Glasses.java32
-rw-r--r--src/main/java/gregtech/api/multitileentity/multiblock/casing/UpgradeCasing.java35
11 files changed, 2304 insertions, 0 deletions
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java
new file mode 100644
index 0000000000..cdcb77d6e5
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java
@@ -0,0 +1,111 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.EnumChatFormatting;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+
+import gregtech.api.logic.ComplexParallelProcessingLogic;
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.GT_Waila;
+import mcp.mobius.waila.api.IWailaConfigHandler;
+import mcp.mobius.waila.api.IWailaDataAccessor;
+
+public abstract class ComplexParallelController<C extends ComplexParallelController<C, P>, P extends ComplexParallelProcessingLogic<P>>
+ extends Controller<C, P> {
+
+ protected int maxComplexParallels = 0;
+ protected int currentComplexParallels = 0;
+
+ public ComplexParallelController() {
+ isSimpleMachine = false;
+ }
+
+ protected void setMaxComplexParallels(int parallel, boolean stopMachine) {
+ if (parallel != maxComplexParallels && maxComplexParallels != 0 && stopMachine) {
+ stopMachine(false);
+ }
+ maxComplexParallels = parallel;
+ setProcessingUpdate(true);
+ }
+
+ @Override
+ protected void stopMachine(boolean powerShutDown) {
+ super.stopMachine(powerShutDown);
+ }
+
+ protected boolean hasPerfectOverclock() {
+ return false;
+ }
+
+ @Override
+ protected void addProgressStringToScanner(EntityPlayer player, int logLevel, ArrayList<String> list) {
+ P processing = getProcessingLogic();
+ for (int i = 0; i < maxComplexParallels; i++) {
+ list.add(
+ StatCollector.translateToLocal("GT5U.multiblock.Progress") + " "
+ + (i + 1)
+ + ": "
+ + EnumChatFormatting.GREEN
+ + GT_Utility.formatNumbers(
+ processing.getProgress(i) > 20 ? processing.getProgress(i) / 20 : processing.getProgress(i))
+ + EnumChatFormatting.RESET
+ + (processing.getProgress(i) > 20 ? " s / " : " ticks / ")
+ + EnumChatFormatting.YELLOW
+ + GT_Utility.formatNumbers(
+ processing.getDuration(i) > 20 ? processing.getDuration(i) / 20 : processing.getDuration(i))
+ + EnumChatFormatting.RESET
+ + (processing.getDuration(i) > 20 ? " s" : " ticks"));
+ }
+ }
+
+ @Override
+ public void getWailaNBTData(EntityPlayerMP player, TileEntity tile, NBTTagCompound tag, World world, int x, int y,
+ int z) {
+ super.getWailaNBTData(player, tile, tag, world, x, y, z);
+ P processing = getProcessingLogic();
+ tag.setInteger("maxComplexParallels", maxComplexParallels);
+ for (int i = 0; i < maxComplexParallels; i++) {
+ tag.setInteger("maxProgress" + i, processing.getDuration(i));
+ tag.setInteger("progress" + i, processing.getProgress(i));
+ }
+ }
+
+ @Override
+ public void getWailaBody(ItemStack itemStack, List<String> currentTip, IWailaDataAccessor accessor,
+ IWailaConfigHandler config) {
+ super.getWailaBody(itemStack, currentTip, accessor, config);
+ final NBTTagCompound tag = accessor.getNBTData();
+ maxComplexParallels = tag.getInteger("maxComplexParallels");
+ for (int i = 0; i < maxComplexParallels; i++) {
+ long maxProgress = tag.getInteger("maxProgress" + i);
+ long progress = tag.getInteger("progress" + i);
+ currentTip.add(
+ "Process " + (i + 1)
+ + ": "
+ + GT_Waila
+ .getMachineProgressString(maxProgress > 0 && maxProgress >= progress, maxProgress, progress));
+ }
+ }
+
+ @Override
+ public void setProcessingLogicPower(@Nonnull P processingLogic) {
+ processingLogic.setAmperageOC(true);
+ processingLogic.setAvailableAmperage(getPowerLogic().getMaxAmperage() / maxComplexParallels);
+ processingLogic.setAvailableVoltage(getPowerLogic().getVoltage() / maxComplexParallels);
+ }
+
+ @Override
+ public void updateProcessingLogic(@Nonnull P processingLogic) {
+ processingLogic.setMaxComplexParallel(maxComplexParallels);
+ }
+}
diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java
new file mode 100644
index 0000000000..552cf6d94e
--- /dev/null
+++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java
@@ -0,0 +1,1082 @@
+package gregtech.api.multitileentity.multiblock.base;
+
+import static gregtech.api.util.GT_Utility.moveMultipleItemStacks;
+import static gregtech.common.misc.WirelessNetworkManager.strongCheckOrAddUser;
+import static mcp.mobius.waila.api.SpecialChars.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.StatCollector;
+import net.minecraft.world.World;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.FluidStack;
+
+import org.jetbrains.annotations.NotNull;
+import org.lwjgl.input.Keyboard;
+
+import com.gtnewhorizon.structurelib.StructureLibAPI;
+import com.gtnewhorizon.structurelib.alignment.IAlignment;
+import com.gtnewhorizon.structurelib.alignment.IAlignmentLimits;
+import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
+import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
+import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
+import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
+import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
+import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
+import com.gtnewhorizon.structurelib.util.Vec3Impl;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+
+import cpw.mods.fml.common.network.NetworkRegistry;
+import gregtech.api.enums.GT_Values.NBT;
+import gregtech.api.enums.InventoryType;
+import gregtech.api.enums.VoidingMode;
+import gregtech.api.interfaces.IDescribable;
+import gregtech.api.interfaces.fluid.IFluidStore;
+import gregtech.api.logic.ControllerFluidLogic;
+import gregtech.api.logic.ControllerItemLogic;
+import gregtech.api.logic.FluidInventoryLogic;
+import gregtech.api.logic.ItemInventoryLogic;
+import gregtech.api.logic.MuTEProcessingLogic;
+import gregtech.api.logic.PowerLogic;
+import gregtech.api.multitileentity.WeakTargetRef;
+import gregtech.api.multitileentity.enums.MultiTileCasingPurpose;
+import gregtech.api.multitileentity.interfaces.IMultiBlockController;
+import gregtech.api.multitileentity.interfaces.IMultiBlockPart;
+import gregtech.api.multitileentity.machine.MultiTileBasicMachine;
+import gregtech.api.multitileentity.multiblock.casing.FunctionalCasing;
+import gregtech.api.multitileentity.multiblock.casing.UpgradeCasing;
+import gregtech.api.net.GT_Packet_MultiTileEntity;
+import gregtech.api.objects.GT_ItemStack;
+import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
+import gregtech.api.util.GT_Utility;
+import gregtech.api.util.GT_Waila;
+import mcp.mobius.waila.api.IWailaConfigHandler;
+import mcp.mobius.waila.api.IWailaDataAccessor;
+
+/**
+ * Multi Tile Entities - or MuTEs - don't have dedicated hatches, but their casings can become hatches.
+ */
+public abstract class Controller<C extends Controller<C, P>, P extends MuTEProcessingLogic<P>> extends
+ MultiTileBasicMachine<P> implements IAlignment, IMultiBlockController, IDescribable, ISurvivalConstructable {
+
+ public static final String ALL_INVENTORIES_NAME = "all";
+ protected static final int AUTO_OUTPUT_FREQUENCY_TICK = 20;
+
+ private static final Map<Integer, GT_Multiblock_Tooltip_Builder> tooltip = new ConcurrentHashMap<>();
+ private final List<WeakTargetRef<UpgradeCasing>> upgradeCasings = new ArrayList<>();
+ private final List<WeakTargetRef<FunctionalCasing>> functionalCasings = new ArrayList<>();
+ protected BuildState buildState = new BuildState();
+
+ private boolean structureOkay = false, structureChanged = false;
+ private ExtendedFacing extendedFacing = ExtendedFacing.DEFAULT;
+ private IAlignmentLimits limits = getInitialAlignmentLimits();
+ protected boolean separateInputs = getDefaultInputSeparationMode();
+ protected VoidingMode voidingMode = getDefaultVoidingMode();
+ protected boolean batchMode = getDefaultBatchMode();
+ protected boolean recipeLock = getDefaultRecipeLockingMode();
+ protected boolean shouldSort = false;
+ /** If this is set to true, the machine will get default WAILA behavior */
+ protected boolean isSimpleMachine = true;
+
+ protected boolean isCleanroom = false;
+ protected ControllerItemLogic controllerItemInput = new ControllerItemLogic();
+ protected ControllerItemLogic controllerItemOutput = new ControllerItemLogic();
+ protected ControllerFluidLogic controllerFluidInput = new ControllerFluidLogic();
+ protected ControllerFluidLogic controllerFluidOutput = new ControllerFluidLogic();
+
+ // A list of sides
+ // Each side has a list of parts that have a cover that need to be ticked
+ protected List<List<WeakTargetRef<IMultiBlockPart>>> registeredCoveredParts = Arrays.asList(
+ new ArrayList<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ new ArrayList<>(),
+ new ArrayList<>());
+
+ // A list for each purpose that a casing can register to, to be ticked
+ protected List<List<WeakTargetRef<IMultiBlockPart>>> registeredTickableParts = new ArrayList<>();
+
+ public Controller() {
+ for (int i = 0; i < MultiTileCasingPurpose.values().length; i++) {
+ registeredTickableParts.add(new ArrayList<>());
+ }
+ }
+
+ /** Registry ID of the required casing */
+ public abstract short getCasingRegistryID();
+
+ /** Meta ID of the required casing */
+ public abstract int getCasingMeta();
+
+ /**
+ * Create the tooltip for this multi block controller.
+ */
+ protected abstract GT_Multiblock_Tooltip_Builder createTooltip();
+
+ /**
+ * @return The starting offset for the structure builder
+ */
+ public abstract Vec3Impl getStartingStructureOffset();
+
+ /**
+ * Due to limitation of Java type system, you might need to do an unchecked cast. HOWEVER, the returned
+ * IStructureDefinition is expected to be evaluated against current instance only, and should not be used against
+ * other instances, even for those of the same class.
+ */
+ @Override
+ public abstract IStructureDefinition<C> getStructureDefinition();
+
+ /**
+ * Checks the Machine.
+ * <p>
+ * NOTE: If using `buildState` be sure to `startBuilding()` and either `endBuilding()` or `failBuilding()`
+ */
+ public boolean checkMachine() {
+ calculateTier();
+ updatePowerLogic();
+ return tier > 0;
+ }
+
+ protected void calculateTier() {
+ if (functionalCasings.size() == 0) {
+ return;
+ }
+ double sum = 0;
+ for (WeakTargetRef<FunctionalCasing> casingRef : functionalCasings) {
+ final FunctionalCasing casing = casingRef.get();
+ if (casing == null) continue;
+ sum += casing.getPartTier() * casing.getPartModifier();
+ }
+ tier = (int) Math.min(Math.floor(sum / functionalCasings.size()), 14);
+ }
+
+ @Override
+ public void writeMultiTileNBT(NBTTagCompound nbt) {
+ super.writeMultiTileNBT(nbt);
+
+ nbt.setBoolean(NBT.STRUCTURE_OK, structureOkay);
+ nbt.setByte(
+ NBT.ROTATION,
+ (byte) extendedFacing.getRotation()
+ .getIndex());
+ nbt.setByte(
+ NBT.FLIP,
+ (byte) extendedFacing.getFlip()
+ .getIndex());
+
+ nbt.setString(NBT.VOIDING_MODE, voidingMode.name);
+ nbt.setBoolean(NBT.SEPARATE_INPUTS, separateInputs);
+ nbt.setBoolean(NBT.RECIPE_LOCK, recipeLock);
+ nbt.setBoolean(NBT.BATCH_MODE, batchMode);
+ }
+
+ @Override
+ protected void saveItemLogic(NBTTagCompound nbt) {
+ NBTTagCompound itemInputNBT = controllerItemInput.saveToNBT();
+ nbt.setTag(NBT.INV_INPUT_LIST, itemInputNBT);
+ NBTTagCompound itemOutputNBT = controllerItemOutput.saveToNBT();
+ nbt.setTag(NBT.INV_OUTPUT_LIST, itemOutputNBT);
+ }
+
+ @Override
+ protected void saveFluidLogic(NBTTagCompound nbt) {
+ NBTTagCompound fluidInputNBT = controllerFluidInput.saveToNBT();
+ nbt.setTag(NBT.TANK_IN, fluidInputNBT);
+ NBTTagCompound fluidOutputNBT = controllerFluidOutput.saveToNBT();
+ nbt.setTag(NBT.TANK_OUT, fluidOutputNBT);
+ }
+
+ @Override
+ public void readMultiTileNBT(NBTTagCompound nbt) {
+ super.readMultiTileNBT(nbt);
+
+ // Multiblock inventories are a collection of inventories. The first inventory is the default internal
+ // inventory, and the others are added by inventory extending blocks.
+
+ structureOkay = nbt.getBoolean(NBT.STRUCTURE_OK);
+ extendedFacing = ExtendedFacing
+ .of(getFrontFacing(), Rotation.byIndex(nbt.getByte(NBT.ROTATION)), Flip.byIndex(nbt.getByte(NBT.FLIP)));
+
+ voidingMode = VoidingMode.fromName(nbt.getString(NBT.VOIDING_MODE));
+ separateInputs = nbt.getBoolean(NBT.SEPARATE_INPUTS);
+ recipeLock = nbt.getBoolean(NBT.RECIPE_LOCK);
+ batchMode = nbt.getBoolean(NBT.BATCH_MODE);
+ }
+
+ @Override
+ protected void loadItemLogic(NBTTagCompound nbt) {
+ if (!nbt.hasKey(NBT.INV_INPUT_LIST) && !nbt.hasKey(NBT.INV_OUTPUT_LIST)) {
+ controllerItemInput.addInventory(new ItemInventoryLogic(16));
+ controllerItemOutput.addInventory(new ItemInventoryLogic(16));
+ return;
+ }
+ controllerItemInput.loadFromNBT(nbt.getCompoundTag(NBT.INV_INPUT_LIST));
+ controllerItemOutput.loadFromNBT(nbt.getCompoundTag(NBT.INV_OUTPUT_LIST));
+ }
+
+ @Override
+ protected void loadFluidLogic(NBTTagCompound nbt) {
+ if (!nbt.hasKey(NBT.TANK_IN) && !nbt.hasKey(NBT.TANK_OUT)) {
+ controllerFluidInput.addInventory(new FluidInventoryLogic(16, 32000));
+ controllerFluidOutput.addInventory(new FluidInventoryLogic(16, 32000));
+ return;
+ }
+ controllerFluidInput.loadFromNBT(nbt.getCompoundTag(NBT.TANK_IN));
+ controllerFluidOutput.loadFromNBT(nbt.getCompoundTag(NBT.TANK_OUT));
+ }
+
+ @Override
+ public void addToolTips(List<String> aList, ItemStack aStack, boolean aF3_H) {
+ aList.addAll(Arrays.asList(getDescription()));
+ }
+
+ @Override
+ public String[] getDescription() {
+ if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
+ return getTooltip().getStructureInformation();
+ }
+
+ return getTooltip().getInformation();
+ }
+
+ @Override
+ protected void addDebugInfo(EntityPlayer aPlayer, int aLogLevel, ArrayList<String> tList) {
+ super.addDebugInfo(aPlayer, aLogLevel, tList);
+ tList.add("Structure ok: " + checkStructure(false));
+ }
+
+ protected int getToolTipID() {
+ return getMultiTileEntityRegistryID() << 16 + getMultiTileEntityID();
+ }
+
+ protected GT_Multiblock_Tooltip_Builder getTooltip() {
+ GT_Multiblock_Tooltip_Builder builder = tooltip.get(getToolTipID());
+ if (builder == null) {
+ builder = createTooltip();
+ tooltip.put(getToolTipID(), builder);
+ }
+ return builder;
+ }
+
+ @Override
+ public boolean checkStructure(boolean aForceReset) {
+ if (!isServerSide()) return structureOkay;
+
+ // Only trigger an update if forced (from onPostTick, generally), or if the structure has changed
+ if ((structureChanged || aForceReset)) {
+ clearSpecialLists();
+ structureOkay = checkMachine();
+ }
+ structureChanged = false;
+ return structureOkay;
+ }
+
+ @Override
+ public void onStructureChange() {
+ structureChanged = true;
+ }
+
+ public final boolean checkPiece(String piece, Vec3Impl offset) {
+ return checkPiece(piece, offset.get0(), offset.get1(), offset.get2());
+ }
+
+ /**
+ * Explanation of the world coordinate these offset means:
+ * <p>
+ * Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped.
+ * <p>
+ * The horizontalOffset would be the number of blocks on the left side of the controller, not counting controller
+ * itself. The verticalOffset would be the number of blocks on the top side of the controller, not counting
+ * controller itself. The depthOffset would be the number of blocks between you and controller, not counting
+ * controller itself.
+ * <p>
+ * All these offsets can be negative.
+ */
+ protected final boolean checkPiece(String piece, int horizontalOffset, int verticalOffset, int depthOffset) {
+ return getCastedStructureDefinition().check(
+ this,
+ piece,
+ getWorld(),
+ getExtendedFacing(),
+ getXCoord(),
+ getYCoord(),
+ getZCoord(),
+ horizontalOffset,
+ verticalOffset,
+ depthOffset,
+ !structureOkay);
+ }
+
+ public final boolean buildPiece(String piece, ItemStack trigger, boolean hintsOnly, Vec3Impl offset) {
+ return buildPiece(piece, trigger, hintsOnly, offset.get0(), offset.get1(), offset.get2());
+ }
+
+ protected final boolean buildPiece(String piece, ItemStack trigger, boolean hintOnly, int horizontalOffset,
+ int verticalOffset, int depthOffset) {
+ return getCastedStructureDefinition().buildOrHints(
+ this,
+ trigger,
+ piece,
+ getWorld(),
+ getExtendedFacing(),
+ getXCoord(),
+ getYCoord(),
+ getZCoord(),
+ horizontalOffset,
+ verticalOffset,
+ depthOffset,
+ hintOnly);
+ }
+
+ protected final int survivalBuildPiece(String piece, ItemStack trigger, Vec3Impl offset, int elementBudget,
+ ISurvivalBuildEnvironment env, boolean check) {
+ return survivalBuildPiece(
+ piece,
+ trigger,
+ offset.get0(),
+ offset.get1(),
+ offset.get2(),
+ elementBudget,
+ env,
+ check);
+ }
+
+ protected final Integer survivalBuildPiece(String piece, ItemStack trigger, int horizontalOffset,
+ int verticalOffset, int depthOffset, int elementBudget, ISurvivalBuildEnvironment env, boolean check) {
+ return getCastedStructureDefinition().survivalBuild(
+ this,
+ trigger,
+ piece,
+ getWorld(),
+ getExtendedFacing(),
+ getXCoord(),
+ getYCoord(),
+ getZCoord(),
+ horizontalOffset,
+ verticalOffset,
+ depthOffset,
+ elementBudget,
+ env,
+ check);
+ }
+
+ @SuppressWarnings("unchecked")
+ private IStructureDefinition<Controller<C, P>> getCastedStructureDefinition() {
+ return (IStructureDefinition<Controller<C, P>>) getStructureDefinition();
+ }
+
+ @Override
+ public ExtendedFacing getExtendedFacing() {
+ return extendedFacing;
+ }
+
+ @Override
+ public void setExtendedFacing(ExtendedFacing newExtendedFacing) {
+ if (extendedFacing == newExtendedFacing) {
+ return;
+ }
+
+ onStructureChange();
+ if (structureOkay) stopMachine(false);
+ extendedFacing = newExtendedFacing;
+ structureOkay = false;
+ if (isServerSide()) {
+ StructureLibAPI.sendAlignment(
+ this,
+ new NetworkRegistry.TargetPoint(
+ getWorld().provider.dimensionId,
+ getXCoord(),
+ getYCoord(),
+ getZCoord(),
+ 512));
+ } else {
+ issueTextureUpdate();
+ }
+
+ }
+
+ @Override
+ public boolean onWrenchRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, ForgeDirection wrenchSide, float aX,
+ float aY, float aZ, ItemStack aTool) {
+ if (wrenchSide != getFrontFacing())
+ return super.onWrenchRightClick(aPlayer, tCurrentItem, wrenchSide, aX, aY, aZ);
+ if (aPlayer.isSneaking()) {
+ // we won't be allowing horizontal flips, as it can be perfectly emulated by rotating twice and flipping
+ // horizontally allowing an extra round of flip make it hard to draw meaningful flip markers in
+ // GT_Proxy#drawGrid
+ toolSetFlip(getFlip().isHorizontallyFlipped() ? Flip.NONE : Flip.HORIZONTAL);
+ } else {
+ toolSetRotation(null);
+ }
+ return true;
+ }
+
+ @Override
+ public void registerCoveredPartOnSide(final ForgeDirection side, IMultiBlockPart part) {
+ if (side == ForgeDirection.UNKNOWN) return;
+
+ final List<WeakTargetRef<IMultiBlockPart>> registeredCovers = registeredCoveredParts.get(side.ordinal());
+ // TODO: Make sure that we're not already registered on this side
+ registeredCovers.add(new WeakTargetRef<>(part, true));
+ }
+
+ @Override
+ public void unregisterCoveredPartOnSide(final ForgeDirection side, IMultiBlockPart aPart) {
+ if (side == ForgeDirection.UNKNOWN) return;
+
+ final List<WeakTargetRef<IMultiBlockPart>> coveredParts = registeredCoveredParts.get(side.ordinal());
+ final Iterator<WeakTargetRef<IMultiBlockPart>> it = coveredParts.listIterator();
+ while (it.hasNext()) {
+ final IMultiBlockPart part = (it.next()).get();
+ if (part == null || part == aPart) it.remove();
+ }
+ }
+
+ @Override
+ public void registerCaseWithPurpose(MultiTileCasingPurpose purpose, IMultiBlockPart part) {
+ final List<WeakTargetRef<IMultiBlockPart>> tickableParts = registeredTickableParts.get(purpose.ordinal());
+ final Iterator<WeakTargetRef<IMultiBlockPart>> it = tickableParts.listIterator();
+ while (it.hasNext()) {
+ final IMultiBlockPart next = (it.next()).get();
+ if (next == null) {
+ it.remove();
+ } else if (next == part) {
+ return;
+ }
+ }
+ tickableParts.add(new WeakTargetRef<>(part, true));
+ }
+
+ @Override
+ public void unregisterCaseWithPurpose(MultiTileCasingPurpose purpose, IMultiBlockPart part) {
+ final List<WeakTargetRef<IMultiBlockPart>> tickableParts = registeredTickableParts.get(purpose.ordinal());
+ final Iterator<WeakTargetRef<IMultiBlockPart>> it = tickableParts.listIterator();
+ while (it.hasNext()) {
+ final IMultiBlockPart next = (it.next()).get();
+ if (next == null || next == part) it.remove();
+ }
+ }
+
+ @Override
+ public void onFirstTick(boolean isServerSide) {
+ super.onFirstTick(isServerSide);
+ if (isServerSide) {
+ checkStructure(true);
+ } else {
+ StructureLibAPI.queryAlignment(this);
+ }
+ }
+
+ private boolean tickCovers() {
+ for (final ForgeDirection side : ForgeDirection.VALID_DIRECTIONS) {
+ // TODO: Tick controller covers, if any
+ final List<WeakTargetRef<IMultiBlockPart>> coveredParts = this.registeredCoveredParts.get(side.ordinal());
+ final Iterator<WeakTargetRef<IMultiBlockPart>> it = coveredParts.listIterator();
+ while (it.hasNext()) {
+ final IMultiBlockPart part = (it.next()).get();
+ if (part == null) {
+ it.remove();
+ continue;
+ }
+ if (!part.tickCoverAtSide(side, mTickTimer)) it.remove();
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onTick(long tick, boolean isServerSide) {
+ tickCovers();
+ }
+
+ @Override
+ public void onPostTick(long tick, boolean isServerSide) {
+ if (!isServerSide) { // client side
+ doActivitySound(getActivitySoundLoop());
+ return;
+ }
+
+ // server side
+ if (tick % 600 == 5) {
+ // Recheck the structure every 30 seconds or so
+ if (!checkStructure(false)) checkStructure(true);
+ }
+ if (checkStructure(false)) {
+ runMachine(tick);
+ pushItemOutputs(tick);
+ pushFluidOutputs(tick);
+
+ } else {
+ stopMachine(false);
+ }
+
+ }
+
+ protected void pushItemOutputs(long tick) {
+ if (tick % AUTO_OUTPUT_FREQUENCY_TICK != 0) return;
+ final List<WeakTargetRef<IMultiBlockPart>> registeredItemOutputs = registeredTickableParts
+ .get(MultiTileCasingPurpose.ItemOutput.ordinal());
+ final Iterator<WeakTargetRef<IMultiBlockPart>> itemOutputIterator = registeredItemOutputs.listIterator();
+ while (itemOutputIterator.hasNext()) {
+ final IMultiBlockPart part = (itemOutputIterator.next()).get();
+ if (part == null) {
+ itemOutputIterator.remove();
+ continue;
+ }
+ if (!part.shouldTick(mTickTimer)) {
+ itemOutputIterator.remove();
+ continue;
+ }
+
+ final IInventory facingInventory = part.getIInventoryAtSide(part.getFrontFacing());
+ if (facingInventory == null) {
+ continue;
+ }
+
+ moveMultipleItemStacks(
+ part,
+ facingInventory,
+ part.getFrontFacing(),
+ part.getBackFacing(),
+ null,
+ false,
+ (byte) 64,
+ (byte) 1,
+ (byte) 64,
+ (byte) 1,
+ part.getSizeInventory());
+ for (int i = 0; i < part.getSizeInventory(); i++) {
+ final ItemStack stack = part.getStackInSlot(i);
+ if (stack != null && stack.stackSize <= 0) {
+ part.setInventorySlotContents(i, null);
+ }
+ }
+
+ }
+ }
+
+ protected void pushFluidOutputs(long tick) {
+ if (tick % AUTO_OUTPUT_FREQUENCY_TICK != 0) return;
+ final List<WeakTargetRef<IMultiBlockPart>> registeredFluidOutputs = registeredTickableParts
+ .get(MultiTileCasingPurpose.FluidOutput.ordinal());
+ final Iterator<WeakTargetRef<IMultiBlockPart>> fluidOutputIterator = registeredFluidOutputs.listIterator();
+ while (fluidOutputIterator.hasNext()) {
+ final IMultiBlockPart part = (fluidOutputIterator.next()).get();
+ if (part == null) {
+ fluidOutputIterator.remove();
+ continue;
+ }
+ if (!part.shouldTick(mTickTimer)) {
+ fluidOutputIterator.remove();
+ }
+ }
+ }
+
+ @Override
+ public void setCleanroom(boolean cleanroom) {
+ isCleanroom = cleanroom;
+ }
+
+ protected void clearSpecialLists() {
+ upgradeCasings.clear();
+ functionalCasings.clear();
+ }
+
+ @Override
+ public final boolean isFacingValid(ForgeDirection facing) {
+ return canSetToDirectionAny(facing);
+ }
+
+ @Override
+ public void onFacingChange() {
+ toolSetDirection(getFrontFacing());
+ onStructureChange();
+ }
+
+ @Override
+ public boolean allowCoverOnSide(ForgeDirection side, GT_ItemStack aCoverID) {
+ return side != facing;
+ }
+
+ @Override
+ public String[] getStructureDescription(ItemStack stackSize) {
+ return getTooltip().getStructureHint();
+ }
+
+ @Override
+ public IAlignmentLimits getAlignmentLimits() {
+ return limits;
+ }
+
+ protected void setAlignmentLimits(IAlignmentLimits mLimits) {
+ this.limits = mLimits;
+ }
+
+ public boolean isSeparateInputs() {
+ return separateInputs;
+ }
+
+ public void setSeparateInputs(boolean aSeparateInputs) {
+ separateInputs = aSeparateInputs;
+ }
+
+ protected IAlignmentLimits getInitialAlignmentLimits() {
+ return (d, r, f) -> !f.isVerticallyFliped();
+ }
+
+ public static class BuildState {
+
+ /**
+ * Utility class to keep track of the build state of a multiblock
+ */
+ boolean building = false;
+
+ Vec3Impl currentOffset;
+
+ public void startBuilding(Vec3Impl structureOffset) {
+ if (building) throw new IllegalStateException("Already building!");
+ building = true;
+ setCurrentOffset(structureOffset);
+ }
+
+ public Vec3Impl setCurrentOffset(Vec3Impl structureOffset) {
+ verifyBuilding();
+ return (currentOffset = structureOffset);
+ }
+
+ private void verifyBuilding() {
+ if (!building) throw new IllegalStateException("Not building!");
+ }
+
+ public boolean failBuilding() {
+ building = false;
+ currentOffset = null;
+ return false;
+ }
+
+ public Vec3Impl stopBuilding() {
+ final Vec3Impl toReturn = getCurrentOffset();
+ building = false;
+ currentOffset = null;
+
+ return toReturn;
+ }
+
+ public Vec3Impl getCurrentOffset() {
+ verifyBuilding();
+ return currentOffset;
+ }
+
+ public Vec3Impl addOffset(Vec3Impl offset) {
+ verifyBuilding();
+ return setCurrentOffset(currentOffset.add(offset));
+ }
+ }
+
+ public void registerSpecialCasings(MultiBlockPart part) {
+ if (part instanceof UpgradeCasing upgradeCasing) {
+ upgradeCasings.add(new WeakTargetRef<>(upgradeCasing, true));
+ }
+ if (part instanceof FunctionalCasing functionalCasing) {
+ functionalCasings.add(new WeakTargetRef<>(functionalCasing, true));
+ }
+ }
+
+ // #region Fluid - MultiBlock related Fluid Tank behaviour.
+
+ @Override
+ @Nullable
+ public FluidInventoryLogic getFluidLogic(@Nonnull ForgeDirection side, @Nonnull InventoryType type) {
+ if (side == facing) return null;
+ return switch (type) {
+ case Input -> controllerFluidInput.getAllInventoryLogics();
+ case Output -> controllerFluidOutput.getAllInventoryLogics();
+ default -> null;
+ };
+ }
+
+ @Override
+ @Nonnull
+ public @NotNull FluidInventoryLogic getFluidLogic(@Nonnull InventoryType type, @Nullable UUID id) {
+ return switch (type) {
+ case Input -> controllerFluidInput.getInventoryLogic(id);
+ case Output -> controllerFluidOutput.getInventoryLogic(id);
+ default -> throw new IllegalStateException("Unexpected value: " + type);
+ };
+ }
+
+ @Override
+ @Nonnull
+ public UUID registerFluidInventory(int tanks, long capacity, int tier, @Nonnull InventoryType type,
+ boolean isUpgradeInventory) {
+ return switch (type) {
+ case Input -> controllerFluidInput
+ .addInventory(new FluidInventoryLogic(tanks, capacity, tier,