From fdde96ab6fef30064b67e28390008ee4ba455655 Mon Sep 17 00:00:00 2001 From: Maxim Date: Sat, 22 Apr 2023 17:38:49 +0200 Subject: MuTE overhaul and ACR (#1883) * complex controller start * Added methods to get input fluids and items * Added logic to complex parallel mute * Added ACR and fixed many, many, many, many bugs * Added void protection setting to checkRecipe * do not init nbt, if mteID and mteRegistry are the same * Improved GUI design * Force structure check when pressing power switch * ACR Textures * Added T1 structure * Added perfect OC * Added WAILA * fix mutes resetting their nbt * Fix ACR GUI * fix npe * Added void protection for MuTEs * Fixed ACR starting recipe while another one is ongoing * nbt saving * maybe fix structure breaking * Fix complex machine disabling on startup * correctly update input tanks * move casings over * Changed logic of casings to change mode and facing in one go by sneaking * Fixed the casing target not resetting * Added side only annotations * don't leave it empty * Added power logic and tiered blocks to ACR * Change facing to wrench side if casing mode is currently none * lasers anyone? * Added ACR item chaining * Remove unncessary item lists * Use HashSet for process whitelists * Optimize list capacities * Fix potential recipe voiding bug * Rename methods for consistancy * Fix NPE * Duct tape fix structure check * allow MuTEs to connect to cables * Added separate tank inventories for input separation (#1887) * Fixed unregistering tank function * Fixed input busses not being automatable * Added fluid chaining * Fixed saving of input tanks * Forbid inventory registering with empty name * Display all input tanks in controller GUI * Fixed fluid hatch GUI height * Reset casing lists when checking the structure * Make inventory GUI size consistant * Make use of the tooltip cache * rename thing clean up * Forgot to put tooltip into map * Added tooltip to ACR * Reset whitelists when one whitelist window was opened * Refined scanner string * Fixed progress times * Fixed MuTE not consuming fluids * Properly register controller inventories * switch to ForgeDirection * switch to new Renderer * Added missing contains check on registerInventory * Fixed output tanks not registering * Fixed upgrade tank loading * fix machines not having active/inactive textures * fix overlays not loading correctly * Don't register controller directly * Remove magic strings all * fix active not setting to inactive * allow glow * item renderer * fix glow * MuTE improved hatch GUI and fluid output locking (#1889) * Allow output hatches to be fluid locked * Reworked hatch GUI * Check target before trying to open GUI * Make ACR GUI easier to look at * fix covers not rendering on mutes * fix covers not displaying above the item/fluid in/out * new folder texture structure * Reduce network traffic caused by covers * Fixed WAILA fluid locking display * Don't save everything to the itemstack NBT * Added possibility to save NBT of MuTE to its itemstack * fix textures, but make sacrifices * mah textures * Removed the need for all textures to be present * Added glow texture for active coke oven * Removed unncesssary upgrade casing textures * shorten nbt tags --------- Co-authored-by: BlueWeabo <76872108+BlueWeabo@users.noreply.github.com> Co-authored-by: Martin Robertz Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../multiblock/base/ComplexParallelController.java | 244 +++ .../multiblock/base/Controller.java | 2187 ++++++++++++++++++++ .../multiblock/base/MultiBlockController.java | 1709 --------------- .../multiblock/base/MultiBlockPart.java | 301 ++- .../multiblock/base/MultiBlockPowerController.java | 44 - .../multiblock/base/MultiBlock_Stackable.java | 111 - .../multiblock/base/PowerController.java | 89 + .../multiblock/base/StackableController.java | 111 + .../multiblock/casing/FunctionalCasing.java | 2 + 9 files changed, 2851 insertions(+), 1947 deletions(-) create mode 100644 src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java create mode 100644 src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java delete mode 100644 src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockController.java delete mode 100644 src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPowerController.java delete mode 100644 src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlock_Stackable.java create mode 100644 src/main/java/gregtech/api/multitileentity/multiblock/base/PowerController.java create mode 100644 src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java (limited to 'src/main/java/gregtech/api/multitileentity/multiblock') 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..ecdeb2294d --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java @@ -0,0 +1,244 @@ +package gregtech.api.multitileentity.multiblock.base; + +import static mcp.mobius.waila.api.SpecialChars.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.LongStream; + +import mcp.mobius.waila.api.IWailaConfigHandler; +import mcp.mobius.waila.api.IWailaDataAccessor; + +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 net.minecraftforge.fluids.FluidStack; + +import gregtech.api.enums.GT_Values; +import gregtech.api.logic.ComplexParallelProcessingLogic; +import gregtech.api.logic.interfaces.PollutionLogicHost; +import gregtech.api.util.GT_Utility; +import gregtech.api.util.GT_Waila; + +public abstract class ComplexParallelController> extends PowerController { + + protected ComplexParallelProcessingLogic processingLogic; + protected int maxComplexParallels = 0; + protected int currentComplexParallels = 0; + protected long[] maxProgressTimes = new long[0]; + protected long[] progressTimes = new long[0]; + + public ComplexParallelController() { + isSimpleMachine = false; + } + + protected void setMaxComplexParallels(int parallel) { + if (parallel != maxComplexParallels) { + if (maxComplexParallels != 0) { + stopMachine(false); + } + maxProgressTimes = new long[parallel]; + progressTimes = new long[parallel]; + } + maxComplexParallels = parallel; + } + + @Override + protected void runMachine(long tick) { + if (acceptsFuel() && isActive()) { + if (!consumeFuel()) { + stopMachine(true); + return; + } + } + + if (hasThingsToDo()) { + markDirty(); + runningTick(tick); + } + if ((tick % TICKS_BETWEEN_RECIPE_CHECKS == 0 || hasWorkJustBeenEnabled() || hasInventoryBeenModified()) + && maxComplexParallels != currentComplexParallels) { + if (isAllowedToWork() && maxComplexParallels > currentComplexParallels) { + wasEnabled = false; + boolean started = false; + for (int i = 0; i < maxComplexParallels; i++) { + if (maxProgressTimes[i] <= 0 && checkRecipe(i)) { + currentComplexParallels++; + started = true; + } + } + if (started) { + setActive(true); + updateSlots(); + markDirty(); + issueClientUpdate(); + } + } + } + } + + @Override + protected void runningTick(long tick) { + consumeEnergy(); + boolean allStopped = true; + for (int i = 0; i < maxComplexParallels; i++) { + if (maxProgressTimes[i] > 0 && ++progressTimes[i] >= maxProgressTimes[i]) { + progressTimes[i] = 0; + maxProgressTimes[i] = 0; + outputItems(i); + outputFluids(i); + if (isAllowedToWork()) { + if (checkRecipe(i)) { + allStopped = false; + } else { + currentComplexParallels--; + } + } + updateSlots(); + } else if (maxProgressTimes[i] > 0) { + allStopped = false; + } + } + if (allStopped) { + setActive(false); + issueClientUpdate(); + } + + if (this instanceof PollutionLogicHost && tick % POLLUTION_TICK == 0) { + doPollution(); + } + emitEnergy(); + } + + protected boolean checkRecipe(int index) { + ComplexParallelProcessingLogic processingLogic = getComplexProcessingLogic(); + if (processingLogic == null || index < 0 || index >= maxComplexParallels) { + return false; + } + processingLogic.clear(index); + boolean result = processingLogic.setInputItems(index, getInputItems(index)) + .setInputFluids(index, getInputFluids(index)) + .setTileEntity(this) + .setVoidProtection(index, isVoidProtectionEnabled(index)) + .setEut(index, getEutForComplexParallel(index)) + .setPerfectOverclock(hasPerfectOverclock()) + .process(index); + setDuration(index, processingLogic.getDuration(index)); + setEut(processingLogic.getTotalEU()); + return result; + } + + protected void outputItems(int index) { + ComplexParallelProcessingLogic processingLogic = getComplexProcessingLogic(); + if (processingLogic != null && index >= 0 && index < maxComplexParallels) { + outputItems(processingLogic.getOutputItems(index)); + } + } + + protected void outputFluids(int index) { + ComplexParallelProcessingLogic processingLogic = getComplexProcessingLogic(); + if (processingLogic != null && index >= 0 && index < maxComplexParallels) { + outputFluids(processingLogic.getOutputFluids(index)); + } + } + + protected ComplexParallelProcessingLogic getComplexProcessingLogic() { + return processingLogic; + } + + @Override + public boolean hasThingsToDo() { + return LongStream.of(maxProgressTimes) + .sum() > 0; + } + + @Override + protected void stopMachine(boolean powerShutDown) { + super.stopMachine(powerShutDown); + for (int i = 0; i < maxComplexParallels; i++) { + maxProgressTimes[i] = 0; + } + } + + protected void setDuration(int index, long duration) { + if (duration < 0) { + duration = -duration; + } + if (index >= 0 && index < maxComplexParallels) { + maxProgressTimes[index] = duration; + } + } + + protected ItemStack[] getInputItems(int index) { + return getInputItems(); + } + + protected FluidStack[] getInputFluids(int index) { + return getInputFluids(); + } + + protected boolean isVoidProtectionEnabled(int index) { + return !voidExcess; + } + + protected boolean hasPerfectOverclock() { + return false; + } + + protected long getEutForComplexParallel(int index) { + // As default behavior we'll give the parallel all remaining EU we have + return GT_Values.V[tier] - eut; + } + + @Override + protected void addProgressStringToScanner(EntityPlayer player, int logLevel, ArrayList list) { + for (int i = 0; i < maxComplexParallels; i++) { + list.add( + StatCollector.translateToLocal("GT5U.multiblock.Progress") + " " + + (i + 1) + + ": " + + EnumChatFormatting.GREEN + + GT_Utility.formatNumbers(progressTimes[i] > 20 ? progressTimes[i] / 20 : progressTimes[i]) + + EnumChatFormatting.RESET + + (progressTimes[i] > 20 ? " s / " : " ticks / ") + + EnumChatFormatting.YELLOW + + GT_Utility + .formatNumbers(maxProgressTimes[i] > 20 ? maxProgressTimes[i] / 20 : maxProgressTimes[i]) + + EnumChatFormatting.RESET + + (maxProgressTimes[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); + tag.setInteger("maxComplexParallels", maxComplexParallels); + for (int i = 0; i < maxComplexParallels; i++) { + tag.setLong("maxProgress" + i, maxProgressTimes[i]); + tag.setLong("progress" + i, progressTimes[i]); + } + } + + @Override + public void getWailaBody(ItemStack itemStack, List 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.getLong("maxProgress" + i); + long progress = tag.getLong("progress" + i); + currentTip.add( + "Process " + (i + 1) + + ": " + + GT_Waila + .getMachineProgressString(maxProgress > 0 && maxProgress >= progress, maxProgress, progress)); + } + } +} 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..9fdac059da --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java @@ -0,0 +1,2187 @@ +package gregtech.api.multitileentity.multiblock.base; + +import static com.gtnewhorizon.structurelib.structure.StructureUtility.ofChain; +import static gregtech.GT_Mod.GT_FML_LOGGER; +import static gregtech.api.enums.GT_Values.ALL_VALID_SIDES; +import static gregtech.api.multitileentity.enums.GT_MultiTileComponentCasing.*; +import static gregtech.loaders.preload.GT_Loader_MultiTileEntities.COMPONENT_CASING_REGISTRY; +import static mcp.mobius.waila.api.SpecialChars.*; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import mcp.mobius.waila.api.IWailaConfigHandler; +import mcp.mobius.waila.api.IWailaDataAccessor; + +import net.minecraft.block.Block; +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.nbt.NBTTagList; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.IIcon; +import net.minecraft.util.StatCollector; +import net.minecraft.world.World; +import net.minecraftforge.common.util.Constants; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidTankInfo; +import net.minecraftforge.fluids.IFluidTank; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +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.IConstructable; +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.IStructureElement; +import com.gtnewhorizon.structurelib.structure.IStructureElementChain; +import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment; +import com.gtnewhorizon.structurelib.util.Vec3Impl; +import com.gtnewhorizons.modularui.api.ModularUITextures; +import com.gtnewhorizons.modularui.api.drawable.IDrawable; +import com.gtnewhorizons.modularui.api.drawable.ItemDrawable; +import com.gtnewhorizons.modularui.api.drawable.UITexture; +import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable; +import com.gtnewhorizons.modularui.api.forge.ItemStackHandler; +import com.gtnewhorizons.modularui.api.forge.ListItemHandler; +import com.gtnewhorizons.modularui.api.screen.*; +import com.gtnewhorizons.modularui.api.widget.Widget; +import com.gtnewhorizons.modularui.common.widget.ButtonWidget; +import com.gtnewhorizons.modularui.common.widget.DrawableWidget; +import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget; +import com.gtnewhorizons.modularui.common.widget.FluidSlotWidget; +import com.gtnewhorizons.modularui.common.widget.MultiChildWidget; +import com.gtnewhorizons.modularui.common.widget.Scrollable; +import com.gtnewhorizons.modularui.common.widget.SlotWidget; +import com.gtnewhorizons.modularui.common.widget.TabButton; +import com.gtnewhorizons.modularui.common.widget.TabContainer; + +import cpw.mods.fml.common.network.NetworkRegistry; +import gnu.trove.list.TIntList; +import gnu.trove.list.array.TIntArrayList; +import gregtech.api.enums.GT_Values; +import gregtech.api.enums.GT_Values.NBT; +import gregtech.api.enums.OrePrefixes; +import gregtech.api.enums.SoundResource; +import gregtech.api.enums.TextureSet; +import gregtech.api.fluid.FluidTankGT; +import gregtech.api.gui.modularui.GT_UITextures; +import gregtech.api.interfaces.IDescribable; +import gregtech.api.logic.PowerLogic; +import gregtech.api.logic.ProcessingLogic; +import gregtech.api.logic.interfaces.PowerLogicHost; +import gregtech.api.logic.interfaces.ProcessingLogicHost; +import gregtech.api.multitileentity.MultiTileEntityContainer; +import gregtech.api.multitileentity.MultiTileEntityRegistry; +import gregtech.api.multitileentity.interfaces.IMultiBlockController; +import gregtech.api.multitileentity.interfaces.IMultiBlockPart; +import gregtech.api.multitileentity.interfaces.IMultiTileEntity; +import gregtech.api.multitileentity.interfaces.IMultiTileEntity.IMTE_AddToolTips; +import gregtech.api.multitileentity.machine.MultiTileBasicMachine; +import gregtech.api.multitileentity.multiblock.casing.FunctionalCasing; +import gregtech.api.multitileentity.multiblock.casing.UpgradeCasing; +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 gregtech.common.tileentities.casings.upgrade.Inventory; + +public abstract class Controller> extends MultiTileBasicMachine implements IAlignment, + IConstructable, IMultiBlockController, IDescribable, IMTE_AddToolTips, ISurvivalConstructable { + + public static final String ALL_INVENTORIES_NAME = "all"; + + private static final Map tooltip = new ConcurrentHashMap<>(); + private final List upgradeCasings = new ArrayList<>(); + private final List functionalCasings = new ArrayList<>(); + protected BuildState buildState = new BuildState(); + + protected Map multiBlockInputInventoryNames = new LinkedHashMap<>(); + protected Map multiBlockOutputInventoryNames = new LinkedHashMap<>(); + protected Map multiBlockInputInventoryToTankLink = new LinkedHashMap<>(); + protected Map multiBlockInputInventory = new LinkedHashMap<>(); + protected Map multiBlockOutputInventory = new LinkedHashMap<>(); + + protected Map multiBlockInputTankNames = new LinkedHashMap<>(); + protected Map multiBlockOutputTankNames = new LinkedHashMap<>(); + protected Map multiBlockInputTank = new LinkedHashMap<>(); + protected Map multiBlockOutputTank = new LinkedHashMap<>(); + + private boolean structureOkay = false, structureChanged = false; + private ExtendedFacing extendedFacing = ExtendedFacing.DEFAULT; + private IAlignmentLimits limits = getInitialAlignmentLimits(); + private String inventoryName; + private String tankName; + protected boolean separateInputs = false; + protected boolean voidExcess = false; + protected boolean batchMode = false; + protected boolean recipeLock = false; + /** If this is set to true, the machine will get default WAILA behavior */ + protected boolean isSimpleMachine = true; + + // A list of sides + // Each side has a list of parts that have a cover that need to be ticked + protected List>> registeredCoveredParts = Arrays.asList( + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>(), + new LinkedList<>()); + + /** 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 getStructureDefinition(); + + /** + * Checks the Machine. + *

+ * NOTE: If using `buildState` be sure to `startBuilding()` and either `endBuilding()` or `failBuilding()` + */ + public boolean checkMachine() { + double sum = 0; + if (functionalCasings == null || functionalCasings.size() == 0) { + return false; + } + for (FunctionalCasing casing : functionalCasings) { + sum += casing.getPartTier() * casing.getPartModifier(); + } + tier = (int) Math.floor(sum / functionalCasings.size()); + // Maximum Energy stores will have a cap of 2 minute work time of current voltage + return tier > 0; + } + + @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()); + + saveUpgradeInventoriesToNBT(nbt); + saveUpgradeTanksToNBT(nbt); + + nbt.setBoolean(NBT.VOID_EXCESS, voidExcess); + nbt.setBoolean(NBT.SEPARATE_INPUTS, separateInputs); + nbt.setBoolean(NBT.RECIPE_LOCK, recipeLock); + nbt.setBoolean(NBT.BATCH_MODE, batchMode); + } + + private void saveUpgradeInventoriesToNBT(NBTTagCompound nbt) { + final NBTTagList inputInvList = new NBTTagList(); + multiBlockInputInventory.forEach((id, inv) -> { + if (!id.equals("controller")) { + final NBTTagCompound tTag = new NBTTagCompound(); + tTag.setString(NBT.UPGRADE_INVENTORY_UUID, id); + tTag.setString(NBT.UPGRADE_INVENTORY_NAME, multiBlockInputInventoryNames.get(id)); + tTag.setInteger(NBT.UPGRADE_INVENTORY_SIZE, inv.getSlots()); + writeInventory(tTag, inv, NBT.INV_INPUT_LIST); + inputInvList.appendTag(tTag); + } + }); + final NBTTagList outputInvList = new NBTTagList(); + multiBlockOutputInventory.forEach((id, inv) -> { + if (!id.equals("controller")) { + final NBTTagCompound tTag = new NBTTagCompound(); + tTag.setString(NBT.UPGRADE_INVENTORY_UUID, id); + tTag.setString(NBT.UPGRADE_INVENTORY_NAME, multiBlockOutputInventoryNames.get(id)); + tTag.setInteger(NBT.UPGRADE_INVENTORY_SIZE, inv.getSlots()); + writeInventory(tTag, inv, NBT.INV_OUTPUT_LIST); + outputInvList.appendTag(tTag); + } + }); + nbt.setTag(NBT.UPGRADE_INVENTORIES_INPUT, inputInvList); + nbt.setTag(NBT.UPGRADE_INVENTORIES_OUTPUT, outputInvList); + } + + private void saveUpgradeTanksToNBT(NBTTagCompound nbt) { + final NBTTagList inputTankList = new NBTTagList(); + multiBlockInputTank.forEach((id, tanks) -> { + if (!id.equals("controller") && tanks != null && tanks.length > 0) { + final NBTTagCompound tTag = new NBTTagCompound(); + tTag.setString(NBT.UPGRADE_TANK_UUID, id); + tTag.setString(NBT.UPGRADE_TANK_NAME, multiBlockInputTankNames.get(id)); + // We assume all tanks in the tank-array are equally sized + tTag.setLong(NBT.UPGRADE_TANK_CAPACITY, tanks[0].capacity()); + tTag.setLong(NBT.UPGRADE_TANK_CAPACITY_MULTIPLIER, tanks[0].getCapacityMultiplier()); + tTag.setInteger(NBT.UPGRADE_TANKS_COUNT, tanks.length); + for (int i = 0; i < tanks.length; i++) { + tanks[i].writeToNBT(tTag, NBT.UPGRADE_TANKS_PREFIX + i); + } + inputTankList.appendTag(tTag); + } + }); + final NBTTagList outputTankList = new NBTTagList(); + multiBlockOutputTank.forEach((id, tanks) -> { + if (!id.equals("controller") && tanks != null && tanks.length > 0) { + final NBTTagCompound tTag = new NBTTagCompound(); + tTag.setString(NBT.UPGRADE_TANK_UUID, id); + tTag.setString(NBT.UPGRADE_TANK_NAME, multiBlockInputTankNames.get(id)); + // We assume all tanks in the tank-array are equally sized + tTag.setLong(NBT.UPGRADE_TANK_CAPACITY, tanks[0].capacity()); + tTag.setLong(NBT.UPGRADE_TANK_CAPACITY_MULTIPLIER, tanks[0].getCapacityMultiplier()); + tTag.setInteger(NBT.UPGRADE_TANKS_COUNT, tanks.length); + for (int i = 0; i < tanks.length; i++) { + tanks[i].writeToNBT(tTag, NBT.UPGRADE_TANKS_PREFIX + i); + } + outputTankList.appendTag(tTag); + } + }); + nbt.setTag(NBT.UPGRADE_TANKS_INPUT, inputTankList); + nbt.setTag(NBT.UPGRADE_TANKS_OUTPUT, outputTankList); + } + + @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. + if (inputInventory != null) registerInventory("controller", "controller", inputInventory, Inventory.INPUT); + if (outputInventory != null) registerInventory("controller", "controller", outputInventory, Inventory.OUTPUT); + + if (inputTanks != null) registerFluidInventory("controller", "controller", inputTanks, Inventory.INPUT); + if (outputTanks != null) registerFluidInventory("controller", "controller", outputTanks, Inventory.OUTPUT); + + structureOkay = nbt.getBoolean(NBT.STRUCTURE_OK); + extendedFacing = ExtendedFacing.of( + ForgeDirection.getOrientation(getFrontFacing()), + Rotation.byIndex(nbt.getByte(NBT.ROTATION)), + Flip.byIndex(nbt.getByte(NBT.FLIP))); + + loadUpgradeInventoriesFromNBT(nbt); + loadUpgradeTanksFromNBT(nbt); + + voidExcess = nbt.getBoolean(NBT.VOID_EXCESS); + separateInputs = nbt.getBoolean(NBT.SEPARATE_INPUTS); + recipeLock = nbt.getBoolean(NBT.RECIPE_LOCK); + batchMode = nbt.getBoolean(NBT.BATCH_MODE); + } + + private void loadUpgradeInventoriesFromNBT(NBTTagCompound nbt) { + final NBTTagList listInputInventories = nbt + .getTagList(NBT.UPGRADE_INVENTORIES_INPUT, Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < listInputInventories.tagCount(); i++) { + final NBTTagCompound nbtInv = listInputInventories.getCompoundTagAt(i); + String invUUID = nbtInv.getString(NBT.UPGRADE_INVENTORY_UUID); + String invName = nbtInv.getString(NBT.UPGRADE_INVENTORY_NAME); + int invSize = nbtInv.getInteger(NBT.UPGRADE_INVENTORY_SIZE); + IItemHandlerModifiable inv = new ItemStackHandler(invSize); + loadInventory(nbtInv, inv, NBT.INV_INPUT_LIST); + registerInventory(invName, invUUID, invSize, Inventory.INPUT); + } + + final NBTTagList listOutputInventories = nbt + .getTagList(NBT.UPGRADE_INVENTORIES_OUTPUT, Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < listOutputInventories.tagCount(); i++) { + final NBTTagCompound nbtInv = listOutputInventories.getCompoundTagAt(i); + String invUUID = nbtInv.getString(NBT.UPGRADE_INVENTORY_UUID); + String invName = nbtInv.getString(NBT.UPGRADE_INVENTORY_NAME); + int invSize = nbtInv.getInteger(NBT.UPGRADE_INVENTORY_SIZE); + IItemHandlerModifiable inv = new ItemStackHandler(invSize); + loadInventory(nbtInv, inv, NBT.INV_OUTPUT_LIST); + registerInventory(invName, invUUID, invSize, Inventory.OUTPUT); + } + } + + private void loadUpgradeTanksFromNBT(NBTTagCompound nbt) { + final NBTTagList listInputTanks = nbt.getTagList(NBT.UPGRADE_TANKS_INPUT, Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < listInputTanks.tagCount(); i++) { + final NBTTagCompound nbtTank = listInputTanks.getCompoundTagAt(i); + String tankUUID = nbtTank.getString(NBT.UPGRADE_TANK_UUID); + String tankName = nbtTank.getString(NBT.UPGRADE_TANK_NAME); + long capacity = nbtTank.getLong(NBT.UPGRADE_TANK_CAPACITY); + long capacityMultiplier = nbtTank.getLong(NBT.UPGRADE_TANK_CAPACITY_MULTIPLIER); + int count = nbtTank.getInteger(NBT.UPGRADE_TANKS_COUNT); + FluidTankGT[] tanks = new FluidTankGT[count]; + for (int j = 0; j < count; j++) { + tanks[j] = new FluidTankGT(capacity).setCapacityMultiplier(capacityMultiplier) + .readFromNBT(nbtTank, NBT.UPGRADE_TANKS_PREFIX + j); + } + registerFluidInventory(tankName, tankUUID, count, capacity, capacityMultiplier, Inventory.INPUT); + } + + final NBTTagList listOutputTanks = nbt.getTagList(NBT.UPGRADE_TANKS_OUTPUT, Constants.NBT.TAG_COMPOUND); + for (int i = 0; i < listOutputTanks.tagCount(); i++) { + final NBTTagCompound nbtTank = listOutputTanks.getCompoundTagAt(i); + String tankUUID = nbtTank.getString(NBT.UPGRADE_TANK_UUID); + String tankName = nbtTank.getString(NBT.UPGRADE_TANK_NAME); + long capacity = nbtTank.getLong(NBT.UPGRADE_TANK_CAPACITY); + long capacityMultiplier = nbtTank.getLong(NBT.UPGRADE_TANK_CAPACITY_MULTIPLIER); + int count = nbtTank.getInteger(NBT.UPGRADE_TANKS_COUNT); + FluidTankGT[] tanks = new FluidTankGT[count]; + for (int j = 0; j < count; j++) { + tanks[j] = new FluidTankGT(capacity).setCapacityMultiplier(capacityMultiplier) + .readFromNBT(nbtTank, NBT.UPGRADE_TANKS_PREFIX + j); + } + registerFluidInventory(tankName, tankUUID, count, capacity, capacityMultiplier, Inventory.OUTPUT); + } + } + + @Override + public void addToolTips(List aList, ItemStack aStack, boolean aF3_H) { + aList.addAll(Arrays.asList(getDescription())); + } + + @Override + public String[] getDescription() { + if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) { + return getTooltip().getStructureInformation(); + } else { + return getTooltip().getInformation(); + } + } + + @Override + protected void addDebugInfo(EntityPlayer aPlayer, int aLogLevel, ArrayList 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)) { + structureOkay = checkMachine(); + } + structureChanged = false; + return structureOkay; + } + + @Override + public void onStructureChange() { + structureChanged = true; + } + + public final boolean checkPiece(String piece, Vec3Impl offset) { + functionalCasings.clear(); + upgradeCasings.clear(); + return checkPiece(piece, offset.get0(), offset.get1(), offset.get2()); + } + + /** + * Explanation of the world coordinate these offset means: + *

+ * Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped. + *

+ * 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. + *

+ * 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> getCastedStructureDefinition() { + return (IStructureDefinition>) getStructureDefinition(); + } + + @Override + public ExtendedFacing getExtendedFacing() { + return extendedFacing; + } + + @Override + public void setExtendedFacing(ExtendedFacing newExtendedFacing) { + if (extendedFacing != newExtendedFacing) { + 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, byte wrenchSide, float aX, float aY, + float aZ) { + 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 int aSide, IMultiBlockPart part) { + if (aSide < 0 || aSide >= 6) return; + + final LinkedList> registeredCovers = registeredCoveredParts.get(aSide); + // TODO: Make sure that we're not already registered on this side + registeredCovers.add(new WeakReference<>(part)); + } + + @Override + public void unregisterCoveredPartOnSide(final int aSide, IMultiBlockPart aPart) { + if (aSide < 0 || aSide >= 6) return; + + final LinkedList> coveredParts = registeredCoveredParts.get(aSide); + final Iterator> it = coveredParts.iterator(); + while (it.hasNext()) { + final IMultiBlockPart part = (it.next()).get(); + if (part == null || part == aPart) it.remove(); + } + } + + @Override + public void onFirstTick(boolean isServerSide) { + super.onFirstTick(isServerSide); + if (isServerSide) { + checkStructure(true); + } else { + StructureLibAPI.queryAlignment(this); + } + } + + private boolean tickCovers() { + for (byte side : ALL_VALID_SIDES) { + // TODO: Tick controller covers, if any + final LinkedList> coveredParts = this.registeredCoveredParts.get(side); + final Iterator> it = coveredParts.iterator(); + 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 timer, boolean isServerSide) { + if (!tickCovers()) { + return; + } + } + + @Override + public void onPostTick(long tick, boolean isServerSide) { + if (isServerSide) { + if (tick % 600 == 5) { + clearSpecialLists(); + // Recheck the structure every 30 seconds or so + if (!checkStructure(false)) checkStructure(true); + } + if (checkStructure(false)) { + runMachine(tick); + } else { + stopMachine(false); + } + } else { + doActivitySound(getActivitySoundLoop()); + } + } + + protected void clearSpecialLists() { + upgradeCasings.clear(); + } + + @Override + public final boolean isFacingValid(byte aFacing) { + return canSetToDirectionAny(ForgeDirection.getOrientation(aFacing)); + } + + @Override + public void onFacingChange() { + toolSetDirection(ForgeDirection.getOrientation(getFrontFacing())); + onStructureChange(); + } + + @Override + public boolean allowCoverOnSide(byte aSide, GT_ItemStack aCoverID) { + return facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0; + } + + @Override + public String[] getStructureDescription(ItemStack stackSize) { + return getTooltip().getStructureHint(); + } + + @Override + public IAlignmentLimits getAlignmentLimits() { + return limits; + } + + protected void setAlignmentLimits(IAlignmentLimits mLimits) { + this.limits = mLimits; + } + + // IMachineProgress + @Override + public long getProgress() { + return progressTime; + } + + @Override + public long getMaxProgress() { + return maxProgressTime; + } + + @Override + public boolean increaseProgress(int aProgressAmountInTicks) { + return increaseProgressGetOverflow(aProgressAmountInTicks) != aProgressAmountInTicks; + } + + @Override + public FluidStack getDrainableFluid(byte aSide) { + return getDrainableFluid(aSide, null); + } + + @Override + public FluidStack getDrainableFluid(byte aSide, Fluid fluidToDrain) { + final IFluidTank tank = getFluidTankDrainable( + aSide, + fluidToDrain == null ? null : new FluidStack(fluidToDrain, 0)); + return tank == null ? null : tank.getFluid(); + } + + /** + * Increases the Progress, returns the overflown Progress. + */ + public int increaseProgressGetOverflow(int aProgress) { + return 0; + } + + @Override + public boolean hasThingsToDo() { + return getMaxProgress() > 0; + } + + public boolean isSeparateInputs() { + return separateInputs; + } + + public void setSeparateInputs(boolean aSeparateInputs) { + separateInputs = aSeparateInputs; + } + + // End IMachineProgress + + 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 IStructureElement addMultiTileCasing(String registryName, int meta, int modes) { + MultiTileEntityRegistry registry = MultiTileEntityRegistry.getRegistry(registryName); + int registryID = Block.getIdFromBlock(registry.mBlock); + return addMultiTileCasing(registryID, meta, modes); + } + + public IStructureElement addMultiTileCasing(int registryID, int meta, int modes) { + return new IStructureElement() { + + private final short[] DEFAULT = new short[] { 255, 255, 255, 0 }; + private IIcon[] mIcons = null; + + @Override + public boolean check(S t, World world, int x, int y, int z) { + final TileEntity tileEntity = world.getTileEntity(x, y, z); + if (!(tileEntity instanceof MultiBlockPart part)) return false; + + if (registryID != part.getMultiTileEntityRegistryID() || meta != part.getMultiTileEntityID()) + return false; + + final IMultiBlockController tTarget = part.getTarget(false); + if (tTarget != null && tTarget != t) return false; + + part.setTarget((IMultiBlockController) t, modes); + + ((Controller) t).registerSpecialCasings(part); + return true; + } + + @Override + public boolean spawnHint(S t, World world, int x, int y, int z, ItemStack trigger) { + if (mIcons == null) { + mIcons = new IIcon[6]; + Arrays.fill(mIcons, TextureSet.SET_NONE.mTextures[OrePrefixes.block.mTextureIndex].getIcon()); + // Arrays.fill(mIcons, getTexture(aCasing); + // for (byte i : ALL_VALID_SIDES) { + // mIcons[i] = aCasing.getIcon(i, aMeta); + // } + } + final short[] RGBA = DEFAULT; + StructureLibAPI.hintParticleTinted(world, x, y, z, mIcons, RGBA); + // StructureLibAPI.hintParticle(world, x, y, z, aCasing, aMeta); + return true; + } + + @Override + public boolean placeBlock(S t, World world, int x, int y, int z, ItemStack trigger) { + final MultiTileEntityRegistry tRegistry = MultiTileEntityRegistry.getRegistry(registryID); + final MultiTileEntityContainer tContainer = tRegistry + .getNewTileEntityContainer(world, x, y, z, meta, null); + if (tContainer == null) { + GT_FML_LOGGER.error("NULL CONTAINER"); + return false; + } + final IMultiTileEntity te = ((IMultiTileEntity) tContainer.mTileEntity); + if (!(te instanceof MultiBlockPart)) { + GT_FML_LOGGER.error("Not a multiblock part"); + return false; + } + if (world.setBlock(x, y, z, tContainer.mBlock, 15 - tContainer.mBlockMetaData, 2)) { + tContainer.setMultiTile(world, x, y, z); + ((MultiBlockPart) te).setTarget(Controller.this, modes); + + ((Controller) t).registerSpecialCasings((MultiBlockPart) te); + } + + return false; + } + + public IIcon getTexture(OrePrefixes aBlock) { + return TextureSet.SET_NONE.mTextures[OrePrefixes.block.mTextureIndex].getIcon(); + } + }; + } + + protected IStructureElementChain addMotorCasings(int modes) { + return ofChain( + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, HV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, EV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, IV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LuV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, ZPM_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UHV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UEV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UIV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UMV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UXV_Motor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MAX_Motor.getId(), modes)); + } + + protected IStructureElementChain addPumpCasings(int modes) { + return ofChain( + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, HV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, EV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, IV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LuV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, ZPM_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UHV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UEV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UIV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UMV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UXV_Pump.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MAX_Pump.getId(), modes)); + } + + protected IStructureElementChain addPistonCasings(int modes) { + return ofChain( + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, HV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, EV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, IV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LuV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, ZPM_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UHV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UEV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UIV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UMV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UXV_Piston.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MAX_Piston.getId(), modes)); + } + + protected IStructureElementChain addConveyorCasings(int modes) { + return ofChain( + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, HV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, EV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, IV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LuV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, ZPM_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UHV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UEV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UIV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UMV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UXV_Conveyor.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MAX_Conveyor.getId(), modes)); + } + + protected IStructureElementChain addRobotArmCasings(int modes) { + return ofChain( + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, HV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, EV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, IV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LuV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, ZPM_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UHV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UEV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UIV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UMV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UXV_RobotArm.getId(), modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MAX_RobotArm.getId(), modes)); + } + + protected IStructureElementChain addSensorCasings(int Modes) { + return ofChain( + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, HV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, EV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, IV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LuV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, ZPM_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UHV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UEV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UIV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UMV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UXV_Sensor.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MAX_Sensor.getId(), Modes)); + } + + protected IStructureElementChain addEmitterCasings(int Modes) { + return ofChain( + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, HV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, EV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, IV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LuV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, ZPM_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UHV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UEV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UIV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UMV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UXV_Emitter.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MAX_Emitter.getId(), Modes)); + } + + protected IStructureElementChain addFieldGeneratorCasings(int Modes) { + return ofChain( + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, HV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, EV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, IV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, LuV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, ZPM_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UHV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UEV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UIV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UMV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, UXV_FieldGenerator.getId(), Modes), + addMultiTileCasing(COMPONENT_CASING_REGISTRY, MAX_FieldGenerator.getId(), Modes)); + } + + protected void registerSpecialCasings(MultiBlockPart part) { + if (part instanceof UpgradeCasing) { + upgradeCasings.add((UpgradeCasing) part); + } + if (part instanceof FunctionalCasing) { + functionalCasings.add((FunctionalCasing) part); + } + } + + /** + * Fluid - MultiBlock related Fluid Tank behaviour. + */ + public void registerFluidInventory(String name, String id, int numberOfSlots, long capacity, + long capacityMultiplier, int type) { + if (name == null || name.equals("") + || id == null + || id.equals("") + || numberOfSlots < 0 + || capacity < 0 + || capacityMultiplier < 0) { + return; + } + FluidTankGT[] tanks = new FluidTankGT[numberOfSlots]; + for (int i = 0; i < numberOfSlots; i++) { + tanks[i] = new FluidTankGT(capacity).setCapacityMultiplier(capacityMultiplier); + } + registerFluidInventory(name, id, tanks, type); + } + + public void registerFluidInventory(String name, String id, FluidTankGT[] fluidInventory, int type) { + if (name == null || name.equals("") + || id == null + || id.equals("") + || fluidInventory == null + || fluidInventory.length == 0) { + return; + } + if (type == Inventory.INPUT || type == Inventory.BOTH) { + if (multiBlockInputTank.containsKey(id)) return; + multiBlockInputTank.put(id, fluidInventory); + multiBlockInputTankNames.put(id, name); + } + if (type == Inventory.OUTPUT || type == Inventory.BOTH) { + if (multiBlockOutputTank.containsKey(id)) return; + multiBlockOutputTank.put(id, fluidInventory); + multiBlockOutputTankNames.put(id, name); + } + } + + public void unregisterFluidInventory(String aName, String aID, int aType) { + if ((aType == Inventory.INPUT || aType == Inventory.BOTH) && multiBlockInputTank.containsKey(aID)) { + multiBlockInputTank.remove(aID, multiBlockInputTank.get(aID)); + multiBlockInputTankNames.remove(aID, aName); + } + if ((aType == Inventory.OUTPUT || aType == Inventory.BOTH) && multiBlockOutputTank.containsKey(aID)) { + multiBlockOutputTank.remove(aID, multiBlockOutputTank.get(aID)); + multiBlockOutputTankNames.remove(aID, aName); + } + } + + protected FluidTankGT[] getTanksForInput() { + List tanks = new ArrayList<>(); + for (FluidTankGT[] inputTanks : multiBlockInputTank.values()) { + tanks.addAll(Arrays.asList(inputTanks)); + } + return tanks.toArray(new FluidTankGT[0]); + } + + protected FluidTankGT[] getTanksForOutput() { + List tanks = new ArrayList<>(); + for (FluidTankGT[] outputTanks : multiBlockOutputTank.values()) { + tanks.addAll(Arrays.asList(outputTanks)); + } + return tanks.toArray(new FluidTankGT[0]); + } + + protected IFluidTank getFluidTankFillable(MultiBlockPart aPart, byte aSide, FluidStack aFluidToFill) { + return getFluidTankFillable(aPart.getFrontFacing(), aSide, aFluidToFill); + } + + protected IFluidTank getFluidTankDrainable(MultiBlockPart aPart, byte aSide, FluidStack aFluidToDrain) { + return getFluidTankDrainable(aPart.getFrontFacing(), aSide, aFluidToDrain); + } + + protected IFluidTank[] getFluidTanks(MultiBlockPart aPart, byte aSide) { + return getFluidTanks(aSide); + } + + @Override + public int fill(MultiBlockPart aPart, ForgeDirection aDirection, FluidStack aFluid, boolean aDoFill) { + if (aFluid == null || aFluid.amount <= 0) return 0; + final IFluidTank tTank = getFluidTankFillable(aPart, (byte) aDirection.ordinal(), aFluid); + if (tTank == null) return 0; + final int rFilledAmount = tTank.fill(aFluid, aDoFill); + if (rFilledAmount > 0 && aDoFill) hasInventoryChanged = true; + return rFilledAmount; + } + + @Override + public FluidStack drain(MultiBlockPart aPart, ForgeDirection aDirection, FluidStack aFluid, boolean aDoDrain) { + if (aFluid == null || aFluid.amount <= 0) return null; + final IFluidTank tTank = getFluidTankDrainable(aPart, (byte) aDirection.ordinal(), aFluid); + if (tTank == null || tTank.getFluid() == null + || tTank.getFluidAmount() == 0 + || !tTank.getFluid() + .isFluidEqual(aFluid)) +