diff options
author | Maxim <maxim235@gmx.de> | 2023-04-22 17:38:49 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-22 08:38:49 -0700 |
commit | fdde96ab6fef30064b67e28390008ee4ba455655 (patch) | |
tree | ee169d0d4a8432433c4ec01eada1e24049a0e47a /src/main/java/gregtech/api/multitileentity/multiblock/base | |
parent | de864236f83dc31c53ca77a6939357a0959bca75 (diff) | |
download | GT5-Unofficial-fdde96ab6fef30064b67e28390008ee4ba455655.tar.gz GT5-Unofficial-fdde96ab6fef30064b67e28390008ee4ba455655.tar.bz2 GT5-Unofficial-fdde96ab6fef30064b67e28390008ee4ba455655.zip |
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 <dream-master@gmx.net>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Diffstat (limited to 'src/main/java/gregtech/api/multitileentity/multiblock/base')
-rw-r--r-- | src/main/java/gregtech/api/multitileentity/multiblock/base/ComplexParallelController.java | 244 | ||||
-rw-r--r-- | src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java (renamed from src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockController.java) | 626 | ||||
-rw-r--r-- | src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java | 301 | ||||
-rw-r--r-- | src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPowerController.java | 44 | ||||
-rw-r--r-- | src/main/java/gregtech/api/multitileentity/multiblock/base/PowerController.java | 89 | ||||
-rw-r--r-- | src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java (renamed from src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlock_Stackable.java) | 2 |
6 files changed, 1104 insertions, 202 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..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<T extends ComplexParallelController<T>> extends PowerController<T> { + + 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<String> 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<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.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/MultiBlockController.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java index 1355ac13a6..9fdac059da 100644 --- a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockController.java +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/Controller.java @@ -1,8 +1,11 @@ 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.enums.GT_Values.NBT; +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; @@ -15,8 +18,12 @@ 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; @@ -24,6 +31,7 @@ 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; @@ -44,6 +52,7 @@ 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; @@ -92,10 +101,13 @@ 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.common.tileentities.casings.upgrade.InventoryUpgrade; +import gregtech.api.util.GT_Waila; +import gregtech.common.tileentities.casings.upgrade.Inventory; + +public abstract class Controller<T extends Controller<T>> extends MultiTileBasicMachine implements IAlignment, + IConstructable, IMultiBlockController, IDescribable, IMTE_AddToolTips, ISurvivalConstructable { -public abstract class MultiBlockController<T extends MultiBlockController<T>> extends MultiTileBasicMachine implements - IAlignment, IConstructable, IMultiBlockController, IDescribable, IMTE_AddToolTips, ISurvivalConstructable { + public static final String ALL_INVENTORIES_NAME = "all"; private static final Map<Integer, GT_Multiblock_Tooltip_Builder> tooltip = new ConcurrentHashMap<>(); private final List<UpgradeCasing> upgradeCasings = new ArrayList<>(); @@ -110,8 +122,8 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex protected Map<String, String> multiBlockInputTankNames = new LinkedHashMap<>(); protected Map<String, String> multiBlockOutputTankNames = new LinkedHashMap<>(); - protected Map<String, FluidTankGT> multiBlockInputTank = new LinkedHashMap<>(); - protected Map<String, FluidTankGT> multiBlockOutputTank = new LinkedHashMap<>(); + protected Map<String, FluidTankGT[]> multiBlockInputTank = new LinkedHashMap<>(); + protected Map<String, FluidTankGT[]> multiBlockOutputTank = new LinkedHashMap<>(); private boolean structureOkay = false, structureChanged = false; private ExtendedFacing extendedFacing = ExtendedFacing.DEFAULT; @@ -122,6 +134,8 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex 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 @@ -137,7 +151,7 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex public abstract short getCasingRegistryID(); /** Meta ID of the required casing */ - public abstract short getCasingMeta(); + public abstract int getCasingMeta(); /** * Create the tooltip for this multi block controller. @@ -164,8 +178,11 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex */ public boolean checkMachine() { double sum = 0; + if (functionalCasings == null || functionalCasings.size() == 0) { + return false; + } for (FunctionalCasing casing : functionalCasings) { - sum += casing.getPartTier(); + 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 @@ -187,6 +204,12 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex .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) { @@ -216,14 +239,54 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex 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) multiBlockInputInventory.put("controller", inputInventory); - if (outputInventory != null) multiBlockOutputInventory.put("controller", outputInventory); + 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( @@ -232,10 +295,17 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex 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, 10); + 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); @@ -243,11 +313,11 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex int invSize = nbtInv.getInteger(NBT.UPGRADE_INVENTORY_SIZE); IItemHandlerModifiable inv = new ItemStackHandler(invSize); loadInventory(nbtInv, inv, NBT.INV_INPUT_LIST); - multiBlockInputInventory.put(invUUID, inv); - multiBlockInputInventoryNames.put(invUUID, invName); + registerInventory(invName, invUUID, invSize, Inventory.INPUT); } - final NBTTagList listOutputInventories = nbt.getTagList(NBT.UPGRADE_INVENTORIES_OUTPUT, 10); + 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); @@ -255,8 +325,41 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex int invSize = nbtInv.getInteger(NBT.UPGRADE_INVENTORY_SIZE); IItemHandlerModifiable inv = new ItemStackHandler(invSize); loadInventory(nbtInv, inv, NBT.INV_OUTPUT_LIST); - multiBlockOutputInventory.put(invUUID, inv); - multiBlockOutputInventoryNames.put(invUUID, invName); + 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); } } @@ -285,7 +388,12 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex } protected GT_Multiblock_Tooltip_Builder getTooltip() { - return createTooltip(); + GT_Multiblock_Tooltip_Builder builder = tooltip.get(getToolTipID()); + if (builder == null) { + builder = createTooltip(); + tooltip.put(getToolTipID(), builder); + } + return builder; } @Override @@ -306,6 +414,8 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex } public final boolean checkPiece(String piece, Vec3Impl offset) { + functionalCasings.clear(); + upgradeCasings.clear(); return checkPiece(piece, offset.get0(), offset.get1(), offset.get2()); } @@ -390,8 +500,8 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex } @SuppressWarnings("unchecked") - private IStructureDefinition<MultiBlockController<T>> getCastedStructureDefinition() { - return (IStructureDefinition<MultiBlockController<T>>) getStructureDefinition(); + private IStructureDefinition<Controller<T>> getCastedStructureDefinition() { + return (IStructureDefinition<Controller<T>>) getStructureDefinition(); } @Override @@ -501,7 +611,7 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex // Recheck the structure every 30 seconds or so if (!checkStructure(false)) checkStructure(true); } - if (structureOkay) { + if (checkStructure(false)) { runMachine(tick); } else { stopMachine(false); @@ -528,7 +638,7 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex @Override public boolean allowCoverOnSide(byte aSide, GT_ItemStack aCoverID) { - return aSide != facing; + return facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0; } @Override @@ -563,7 +673,14 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex @Override public FluidStack getDrainableFluid(byte aSide) { - final IFluidTank tank = getFluidTankDrainable(aSide, null); + 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(); } @@ -663,11 +780,11 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex return false; final IMultiBlockController tTarget = part.getTarget(false); - if (tTarget != null && tTarget != MultiBlockController.this) return false; + if (tTarget != null && tTarget != t) return false; - part.setTarget(MultiBlockController.this, modes); + part.setTarget((IMultiBlockController) t, modes); - registerSpecialCasings(part); + ((Controller<?>) t).registerSpecialCasings(part); return true; } @@ -703,9 +820,9 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex } if (world.setBlock(x, y, z, tContainer.mBlock, 15 - tContainer.mBlockMetaData, 2)) { tContainer.setMultiTile(world, x, y, z); - ((MultiBlockPart) te).setTarget(MultiBlockController.this, modes); + ((MultiBlockPart) te).setTarget(Controller.this, modes); - registerSpecialCasings((MultiBlockPart) te); + ((Controller<?>) t).registerSpecialCasings((MultiBlockPart) te); } return false; @@ -717,6 +834,150 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex }; } + protected <S> IStructureElementChain<S> 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 <S> IStructureElementChain<S> 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 <S> IStructureElementChain<S> 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 <S> IStructureElementChain<S> 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 <S> IStructureElementChain<S> 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 <S> IStructureElementChain<S> 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 <S> IStructureElementChain<S> 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 <S> IStructureElementChain<S> 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); @@ -729,12 +990,76 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex /** * 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<FluidTankGT> tanks = new ArrayList<>(); + for (FluidTankGT[] inputTanks : multiBlockInputTank.values()) { + tanks.addAll(Arrays.asList(inputTanks)); + } + return tanks.toArray(new FluidTankGT[0]); + } + + protected FluidTankGT[] getTanksForOutput() { + List<FluidTankGT> 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(aSide, aFluidToFill); + return getFluidTankFillable(aPart.getFrontFacing(), aSide, aFluidToFill); } protected IFluidTank getFluidTankDrainable(MultiBlockPart aPart, byte aSide, FluidStack aFluidToDrain) { - return getFluidTankDrainable(aSide, aFluidToDrain); + return getFluidTankDrainable(aPart.getFrontFacing(), aSide, aFluidToDrain); } protected IFluidTank[] getFluidTanks(MultiBlockPart aPart, byte aSide) { @@ -802,19 +1127,27 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex @Override public IFluidTank[] getFluidTanksForGUI(MultiBlockPart aPart) { - if (aPart.modeSelected(MultiBlockPart.FLUID_IN)) return inputTanks; - if (aPart.modeSelected(MultiBlockPart.FLUID_OUT)) return outputTanks; + final String lockedInventory = aPart.getLockedInventory(); + if (lockedInventory == null) { + if (aPart.modeSelected(MultiBlockPart.FLUID_IN)) return getTanksForInput(); + else if (aPart.modeSelected(MultiBlockPart.FLUID_OUT)) return getTanksForOutput(); + } else { + final Map<String, FluidTankGT[]> tankMap = getMultiBlockTankArray(aPart); + if (tankMap == null) return GT_Values.emptyFluidTank; + final FluidTankGT[] tanks = tankMap.get(lockedInventory); + return tanks != null ? tanks : GT_Values.emptyFluidTank; + } return GT_Values.emptyFluidTank; } // #region Energy @Override - public PowerLogic getPowerLogic(IMultiBlockPart part, byte side) { + public PowerLogic getPowerLogic(IMultiBlockPart part, ForgeDirection side) { if (!(this instanceof PowerLogicHost)) { return null; } - if (part.getFrontFacing() != side) { + if (ForgeDirection.getOrientation(part.getFrontFacing()) != side) { return null; } @@ -827,27 +1160,32 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex */ @Override public void registerInventory(String aName, String aID, int aInventorySize, int aType) { - if (aType == InventoryUpgrade.INPUT || aType == InventoryUpgrade.BOTH) { - if (multiBlockInputInventory.containsKey(aID)) return; - multiBlockInputInventory.put(aID, new ItemStackHandler(aInventorySize)); - multiBlockInputInventoryNames.put(aID, aName); + registerInventory(aName, aID, new ItemStackHandler(aInventorySize), aType); + } + + public void registerInventory(String name, String id, IItemHandlerModifiable inventory, int type) { + if (name == null || name.equals("") || id == null || id.equals("") || inventory == null) { + return; } - if (aType == InventoryUpgrade.OUTPUT || aType == InventoryUpgrade.BOTH) { - if (multiBlockOutputInventory.containsKey(aID)) return; - multiBlockOutputInventory.put(aID, new ItemStackHandler(aInventorySize)); - multiBlockOutputInventoryNames.put(aID, aName); + if (type == Inventory.INPUT || type == Inventory.BOTH) { + if (multiBlockInputInventory.containsKey(id)) return; + multiBlockInputInventory.put(id, inventory); + multiBlockInputInventoryNames.put(id, name); + } + if (type == Inventory.OUTPUT || type == Inventory.BOTH) { + if (multiBlockOutputInventory.containsKey(id)) return; + multiBlockOutputInventory.put(id, inventory); + multiBlockOutputInventoryNames.put(id, name); } } @Override public void unregisterInventory(String aName, String aID, int aType) { - if ((aType == InventoryUpgrade.INPUT || aType == InventoryUpgrade.BOTH) - && multiBlockInputInventory.containsKey(aID)) { + if ((aType == Inventory.INPUT || aType == Inventory.BOTH) && multiBlockInputInventory.containsKey(aID)) { multiBlockInputInventory.remove(aID, multiBlockInputInventory.get(aID)); multiBlockInputInventoryNames.remove(aID, aName); } - if ((aType == InventoryUpgrade.OUTPUT || aType == InventoryUpgrade.BOTH) - && multiBlockOutputInventory.containsKey(aID)) { + if ((aType == Inventory.OUTPUT || aType == Inventory.BOTH) && multiBlockOutputInventory.containsKey(aID)) { multiBlockOutputInventory.remove(aID, multiBlockOutputInventory.get(aID)); multiBlockOutputInventoryNames.remove(aID, aName); } @@ -855,12 +1193,10 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex @Override public void changeInventoryName(String aName, String aID, int aType) { - if ((aType == InventoryUpgrade.INPUT || aType == InventoryUpgrade.BOTH) - && multiBlockInputInventoryNames.containsKey(aID)) { + if ((aType == Inventory.INPUT || aType == Inventory.BOTH) && multiBlockInputInventoryNames.containsKey(aID)) { multiBlockInputInventoryNames.put(aID, aName); } - if ((aType == InventoryUpgrade.OUTPUT || aType == InventoryUpgrade.BOTH) - && multiBlockOutputInventoryNames.containsKey(aID)) { + if ((aType == Inventory.OUTPUT || aType == Inventory.BOTH) && multiBlockOutputInventoryNames.containsKey(aID)) { multiBlockOutputInventoryNames.put(aID, aName); } } @@ -879,10 +1215,18 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex } @Override + public void enableWorking() { + super.enableWorking(); + if (!structureOkay) { + checkStructure(true); + } + } + + @Override public IItemHandlerModifiable getInventoryForGUI(MultiBlockPart aPart) { if (isServerSide()) { for (UpgradeCasing tPart : upgradeCasings) { - if (!(tPart instanceof InventoryUpgrade)) continue; + if (!(tPart instanceof Inventory)) continue; tPart.issueClientUpdate(); } } @@ -908,6 +1252,18 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex return false; } + protected Map<String, FluidTankGT[]> getMultiBlockTankArray(MultiBlockPart aPart) { + if (aPart.modeSelected(MultiBlockPart.FLUID_IN)) return multiBlockInputTank; + else if (aPart.modeSelected(MultiBlockPart.FLUID_OUT)) return multiBlockOutputTank; + return null; + } + + protected Map<String, String> getMultiBlockTankArrayNames(MultiBlockPart aPart) { + if (aPart.modeSelected(MultiBlockPart.FLUID_IN)) return multiBlockInputTankNames; + else if (aPart.modeSelected(MultiBlockPart.FLUID_OUT)) return multiBlockOutputTankNames; + return null; + } + protected Map<String, IItemHandlerModifiable> getMultiBlockInventory(MultiBlockPart aPart) { if (aPart.modeSelected(MultiBlockPart.ITEM_IN)) return multiBlockInputInventory; else if (aPart.modeSelected(MultiBlockPart.ITEM_OUT)) return multiBlockOutputInventory; @@ -1058,8 +1414,7 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex @Override public List<String> getInventoryNames(MultiBlockPart aPart) { final List<String> inventoryNames = new ArrayList<>(); - inventoryNames.add("all"); - inventoryNames.add("controller"); + inventoryNames.add(ALL_INVENTORIES_NAME); inventoryNames.addAll(getMultiBlockInventoryNames(aPart).values()); return inventoryNames; } @@ -1067,7 +1422,7 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex @Override public List<String> getInventoryIDs(MultiBlockPart aPart) { final List<String> tInventoryIDs = new ArrayList<>(); - tInventoryIDs.add("all"); + tInventoryIDs.add(ALL_INVENTORIES_NAME); tInventoryIDs.addAll(getMultiBlockInventory(aPart).keySet()); return tInventoryIDs; } @@ -1095,6 +1450,22 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex } @Override + public List<String> getTankArrayNames(MultiBlockPart aPart) { + final List<String> inventoryNames = new ArrayList<>(); + inventoryNames.add(ALL_INVENTORIES_NAME); + inventoryNames.addAll(getMultiBlockTankArrayNames(aPart).values()); + return inventoryNames; + } + + @Override + public List<String> getTankArrayIDs(MultiBlockPart aPart) { + final List<String> inventoryIDs = new ArrayList<>(); + inventoryIDs.add(ALL_INVENTORIES_NAME); + inventoryIDs.addAll(getMultiBlockTankArray(aPart).keySet()); + return inventoryIDs; + } + + @Override public boolean hasCustomInventoryName(MultiBlockPart aPart) { return hasCustomInventoryName(); } @@ -1137,12 +1508,13 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex * Helper Methods For Recipe checking */ - protected ItemStack[] getAllItemInputs() { + @Override + protected ItemStack[] getInputItems() { return getInventoriesForInput().getStacks() .toArray(new ItemStack[0]); } - protected ItemStack[] getAllOutputItems() { + protected ItemStack[] getOutputItems() { return getInventoriesForOutput().getStacks() .toArray(new ItemStack[0]); } @@ -1159,6 +1531,53 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex .collect(Collectors.toList()); } + protected ItemStack[] getItemInputsForInventory(String id) { + IItemHandlerModifiable inventory = multiBlockInputInventory.get(id); + if (inventory != null) { + return inventory.getStacks() + .toArray(new ItemStack[0]); + } + return null; + } + + @Override + protected FluidStack[] getInputFluids() { + List<FluidStack> fluidStacks = new ArrayList<>(); + for (FluidTankGT[] inputTanks : multiBlockInputTank.values()) { + for (FluidTankGT inputTank : inputTanks) { + FluidStack fluidStack = inputTank.get(); + if (fluidStack != null) { + fluidStacks.add(fluidStack); + } + } + } + return fluidStacks.toArray(new FluidStack[0]); + } + + protected FluidStack[] getOutputFluids() { + List<FluidStack> fluidStacks = new ArrayList<>(); + for (FluidTankGT[] inputTanks : multiBlockInputTank.values()) { + for (FluidTankGT inputTank : inputTanks) { + FluidStack fluidStack = inputTank.getFluid(); + if (fluidStack != null) { + fluidStacks.add(fluidStack); + } + } + } + return fluidStacks.toArray(new FluidStack[0]); + } + + protected Iterable<Pair<FluidStack[], String>> getFluidInputsForEachTankArray() { + return multiBlockInputTank.entrySet() + .stream() + .map((entry) -> Pair.of(FluidTankGT.getFluidsFromTanks(entry.getValue()), entry.getKey())) + .collect(Collectors.toList()); + } + + protected FluidStack[] getFluidInputsForTankArray(String id) { + return FluidTankGT.getFluidsFromTanks(multiBlockInputTank.get(id)); + } + protected void setItemOutputs(String inventory, ItemStack... itemOutputs) { itemsToOutput = itemOutputs; inventoryName = inventory; @@ -1208,7 +1627,7 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex return; } - List<FluidTankGT> tanks = new ArrayList<>(multiBlockOutputTank.values()); + List<FluidTankGT> tanks = Arrays.asList(outputTanks); for (FluidStack fluid : fluidsToOutput) { int index = 0; while (fluid != null && fluid.amount > 0 && index < tanks.size()) { @@ -1223,10 +1642,29 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex protected void updateSlots() { IItemHandlerModifiable inv = getInventoriesForInput(); for (int i = 0; i < inv.getSlots(); i++) { - if (inv.getStackInSlot(i).stackSize <= 0) { + if (inv.getStackInSlot(i) != null && inv.getStackInSlot(i).stackSize <= 0) { inv.setStackInSlot(i, null); } } + + for (FluidTankGT inputTank : getTanksForInput()) { + if (inputTank == null) { + continue; + } + + if (inputTank.get() != null && inputTank.get().amount <= 0) { + inputTank.setEmpty(); + continue; + } + + FluidStack afterRecipe = inputTank.get(); + FluidStack beforeRecipe = inputTank.get(Integer.MAX_VALUE); + if (afterRecipe == null || beforeRecipe == null) { + continue; + } + int difference = beforeRecipe.amount - afterRecipe.amount; + inputTank.remove(difference); + } } @Override @@ -1238,13 +1676,12 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex logic.clear(); boolean result = false; if (isSeparateInputs()) { + // TODO: Add separation with fluids for (Pair<ItemStack[], String> inventory : getItemInputsForEachInventory()) { IItemHandlerModifiable outputInventory = multiBlockOutputInventory .getOrDefault(inventory.getLeft(), null); result = logic.setInputItems(inventory.getLeft()) - .setCurrentOutputItems( - outputInventory != null ? outputInventory.getStacks() - .toArray(new ItemStack[0]) : null) + .setCurrentOutputItems(getOutputItems()) .process(); if (result) { inventoryName = inventory.getRight(); @@ -1253,8 +1690,10 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex logic.clear(); } } else { - result = logic.setInputItems(getAllItemInputs()) - .setCurrentOutputItems(getAllOutputItems()) + result = logic.setInputItems(getInputItems()) + .setCurrentOutputItems(getOutputItems()) + .setInputFluids(getInputFluids()) + .setCurrentOutputFluids(getOutputFluids()) .process(); } setDuration(logic.getDuration()); @@ -1264,6 +1703,14 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex return result; } + public IItemHandlerModifiable getOutputInventory() { + return outputInventory; + } + + public FluidTankGT[] getOutputTanks() { + return outputTanks; + } + /* * GUI Work - Multiblock GUI related methods */ @@ -1314,7 +1761,7 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) { if (isServerSide()) { for (UpgradeCasing tPart : upgradeCasings) { - if (!(tPart instanceof InventoryUpgrade)) continue; + if (!(tPart instanceof Inventory)) continue; tPart.issueClientUpdate(); } } @@ -1340,12 +1787,12 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex new TabButton(page++) .setBackground( false, - ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0, 1f, 0.5f), + ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f), GT_UITextures.PICTURE_ITEM_IN.withFixedSize(16, 16) .withOffset(2, 4)) .setBackground( true, - ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0.5f, 1f, 1f), + ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f), GT_UITextures.PICTURE_ITEM_IN.withFixedSize(16, 16) .withOffset(2, 4)) .setPos(20 * (page - 1), -20)) @@ -1360,12 +1807,12 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex new TabButton(page++) .setBackground( false, - ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0, 1f, 0.5f), + ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f), GT_UITextures.PICTURE_ITEM_OUT.withFixedSize(16, 16) .withOffset(2, 4)) .setBackground( true, - ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0.5f, 1f, 1f), + ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f), GT_UITextures.PICTURE_ITEM_OUT.withFixedSize(16, 16) .withOffset(2, 4)) .setPos(20 * (page - 1), -20)) @@ -1380,12 +1827,12 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex new TabButton(page++) .setBackground( false, - ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0, 1f, 0.5f), + ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f), GT_UITextures.PICTURE_FLUID_IN.withFixedSize(16, 16) .withOffset(2, 4)) .setBackground( true, - ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0.5f, 1f, 1f), + ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f), GT_UITextures.PICTURE_FLUID_IN.withFixedSize(16, 16) .withOffset(2, 4)) .setPos(20 * (page - 1), -20)) @@ -1400,12 +1847,12 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex new TabButton(page++) .setBackground( false, - ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0, 1f, 0.5f), + ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0, 1f, 0.5f), GT_UITextures.PICTURE_FLUID_OUT.withFixedSize(16, 16) .withOffset(2, 4)) .setBackground( true, - ModularUITextures.VANILLA_TAB_TOP_START.getSubArea(0, 0.5f, 1f, 1f), + ModularUITextures.VANILLA_TAB_TOP_MIDDLE.getSubArea(0, 0.5f, 1f, 1f), GT_UITextures.PICTURE_FLUID_OUT.withFixedSize(16, 16) .withOffset(2, 4)) .setPos(20 * (page - 1), -20)) @@ -1487,7 +1934,7 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex } protected Widget getFluidInventoryInputGUI() { - final IFluidTank[] tanks = inputTanks; + final IFluidTank[] tanks = getTanksForInput(); final Scrollable scrollable = new Scrollable().setVerticalScroll(); for (int rows = 0; rows * 4 < tanks.length; rows++) { final int columnsToMake = Math.min(tanks.length - rows * 4, 4); @@ -1498,12 +1945,12 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex .setSize(18, 18)); } } - return scrollable.setSize(18 * 4 + 4, 18 * 4) + return scrollable.setSize(18 * 4 + 4, 18 * 5) .setPos(52, 7); } protected Widget getFluidInventoryOutputGUI() { - final IFluidTank[] tanks = outputTanks; + final IFluidTank[] tanks = getTanksForOutput(); final Scrollable scrollable = new Scrollable().setVerticalScroll(); for (int rows = 0; rows * 4 < tanks.length; rows++) { final int columnsToMake = Math.min(tanks.length - rows * 4, 4); @@ -1706,4 +2153,35 @@ public abstract class MultiBlockController<T extends MultiBlockController<T>> ex protected boolean isRecipeLockingEnabled() { return recipeLock; } + + @Override + public ModularWindow createWindowGUI(UIBuildContext buildContext) { + return createWindow(buildContext); + } + + @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.setLong("progress", progressTime); + tag.setLong("maxProgress", maxProgressTime); + tag.setBoolean("structureOkay", structureOkay); + } + + @Override + public void getWailaBody(ItemStack itemStack, List<String> currentTip, IWailaDataAccessor accessor, + IWailaConfigHandler config) { + super.getWailaBody(itemStack, currentTip, accessor, config); + final NBTTagCompound tag = accessor.getNBTData(); + if (!tag.getBoolean("structureOkay")) { + currentTip.add(RED + "** INCOMPLETE STRUCTURE **" + RESET); + } else { + currentTip.add((GREEN + "Running Fine") + RESET); + } + if (isSimpleMachine) { + boolean isActive = tag.getBoolean("isActive"); + currentTip + .add(GT_Waila.getMachineProgressString(isActive, tag.getLong("maxProgress"), tag.getLong("progress"))); + } + } } diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java index 936145daa8..d9d0ef4666 100644 --- a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPart.java @@ -18,12 +18,12 @@ import static org.apache.commons.lang3.ObjectUtils.firstNonNull; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; 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; @@ -40,19 +40,14 @@ import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable; import com.gtnewhorizons.modularui.api.screen.ModularWindow; import com.gtnewhorizons.modularui.api.screen.ModularWindow.Builder; import com.gtnewhorizons.modularui.api.screen.UIBuildContext; -import com.gtnewhorizons.modularui.common.widget.DrawableWidget; -import com.gtnewhorizons.modularui.common.widget.DropDownWidget; -import com.gtnewhorizons.modularui.common.widget.FluidSlotWidget; -import com.gtnewhorizons.modularui.common.widget.Scrollable; -import com.gtnewhorizons.modularui.common.widget.SlotWidget; +import com.gtnewhorizons.modularui.common.widget.*; import gregtech.api.enums.GT_Values; -import gregtech.api.enums.Textures; -import gregtech.api.interfaces.IIconContainer; +import gregtech.api.fluid.FluidTankGT; +import gregtech.api.gui.modularui.GT_UITextures; import gregtech.api.interfaces.ITexture; import gregtech.api.logic.PowerLogic; import gregtech.api.logic.interfaces.PowerLogicHost; -import gregtech.api.multitileentity.MultiTileEntityRegistry; import gregtech.api.multitileentity.base.NonTickableMultiTileEntity; import gregtech.api.multitileentity.interfaces.IMultiBlockController; import gregtech.api.multitileentity.interfaces.IMultiBlockPart; @@ -80,6 +75,7 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity protected String mLockedInventory = GT_Values.E; protected int mLockedInventoryIndex = 0; + protected FluidTankGT configurationTank = new FluidTankGT(); /** * What Part Tier is this part? All Basic Casings are Tier 1, and will allow: Energy, Item, Fluid input/output. Some @@ -90,12 +86,22 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity } public String getLockedInventory() { - issueClientUpdate(); + // TODO: Can this cause side-effects? Removed for now because it causes huge network traffic when using covers + // issueClientUpdate(); IMultiBlockController controller = getTarget(false); - if (!getNameOfInventoryFromIndex(controller, mLockedInventoryIndex).equals(mLockedInventory)) { - mLockedInventory = getNameOfInventoryFromIndex(controller, mLockedInventoryIndex); - if (mLockedInventory.equals("all")) { - mLockedInventory = ""; + if (modeSelected(ITEM_IN) || modeSelected(ITEM_OUT)) { + if (!getNameOfInventoryFromIndex(controller, mLockedInventoryIndex).equals(mLockedInventory)) { + mLockedInventory = getNameOfInventoryFromIndex(controller, mLockedInventoryIndex); + if (mLockedInventory.equals(Controller.ALL_INVENTORIES_NAME)) { + mLockedInventory = ""; + } + } + } else { + if (!getNameOfTankArrayFromIndex(controller, mLockedInventoryIndex).equals(mLockedInventory)) { + mLockedInventory = getNameOfTankArrayFromIndex(controller, mLockedInventoryIndex); + if (mLockedInventory.equals(Controller.ALL_INVENTORIES_NAME)) { + mLockedInventory = ""; + } } } return mLockedInventory.equals("") ? null : mLockedInventory; @@ -124,6 +130,17 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity IWailaConfigHandler config) { super.getWailaBody(itemStack, currenttip, accessor, config); currenttip.add(String.format("Mode: %s", getModeName(mMode))); + if (modeSelected(FLUID_OUT)) { + if (configurationTank != null && configurationTank.get() != null) { + currenttip.add( + String.format( + "Locked to: %s", + configurationTank.get() + .getLocalizedName())); + } else { + currenttip.add("Locked to: Nothing"); + } + } } public IMultiBlockController getTarget(boolean aCheckValidity) { @@ -202,6 +219,9 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity if (aNBT.hasKey(NBT.LOCKED_INVENTORY_INDEX)) { mLockedInventoryIndex = aNBT.getInteger(NBT.LOCKED_INVENTORY_INDEX); } + if (aNBT.hasKey(NBT.LOCKED_FLUID)) { + configurationTank.readFromNBT(aNBT, NBT.LOCKED_FLUID); + } } @Override @@ -220,6 +240,7 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity if (mLockedInventoryIndex != 0) { aNBT.setInteger(NBT.LOCKED_INVENTORY_INDEX, mLockedInventoryIndex); } + configurationTank.writeToNBT(aNBT, NBT.LOCKED_FLUID); } @Override @@ -318,46 +339,48 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity } @Override - public void loadTextureNBT(NBTTagCompound aNBT) { - // Loading the registry - final String textureName = aNBT.getString(NBT.TEXTURE); - textures = new IIconContainer[] { - new Textures.BlockIcons.CustomIcon("multitileentity/multiblockparts/" + textureName + "/bottom"), - new Textures.BlockIcons.CustomIcon("multitileentity/multiblockparts/" + textureName + "/top"), - new Textures.BlockIcons.CustomIcon("multitileentity/multiblockparts/" + textureName + "/side"), - new Textures.BlockIcons.CustomIcon("multitileentity/multiblockparts/" + textureName + "/overlay/bottom"), - new Textures.BlockIcons.CustomIcon("multitileentity/multiblockparts/" + textureName + "/overlay/top"), - new Textures.BlockIcons.CustomIcon("multitileentity/multiblockparts/" + textureName + "/overlay/side") }; - } - - @Override - public void copyTextures() { - // Loading an instance - final TileEntity tCanonicalTileEntity = MultiTileEntityRegistry - .getCanonicalTileEntity(getMultiTileEntityRegistryID(), getMultiTileEntityID()); - if (tCanonicalTileEntity instanceof MultiBlockPart) textures = ((MultiBlockPart) tCanonicalTileEntity).textures; - } - - @Override - public ITexture[] getTexture(Block aBlock, byte aSide, boolean isActive, int aRenderPass) { - // For normal parts - texture comes from BaseMTE; overlay based on current mode - // TODO(MTE) - For Advanced parts they might come from somewhere else - final ITexture baseTexture = TextureFactory.of(super.getTexture(aBlock, aSide, isActive, aRenderPass)); - if (mMode != 0 && aSide == facing) { - if (mMode == getModeOrdinal(ITEM_IN)) return new ITexture[] { baseTexture, - TextureFactory.of(OVERLAY_PIPE_IN), TextureFactory.of(ITEM_IN_SIGN) }; - if (mMode == getModeOrdinal(ITEM_OUT)) return new ITexture[] { baseTexture, - TextureFactory.of(OVERLAY_PIPE_OUT), TextureFactory.of(ITEM_OUT_SIGN) }; - if (mMode == getModeOrdinal(FLUID_IN)) return new ITexture[] { baseTexture, - TextureFactory.of(OVERLAY_PIPE_IN), TextureFactory.of(FLUID_IN_SIGN) }; - if (mMode == getModeOrdinal(FLUID_OUT)) return new ITexture[] { baseTexture, - TextureFactory.of(OVERLAY_PIPE_OUT), TextureFactory.of(FLUID_OUT_SIGN) }; - if (mMode == getModeOrdinal(ENERGY_IN)) - return new ITexture[] { baseTexture, TextureFactory.of(OVERLAY_ENERGY_IN_MULTI) }; - if (mMode == getModeOrdinal(ENERGY_OUT)) - return new ITexture[] { baseTexture, TextureFactory.of(OVERLAY_ENERGY_OUT_MULTI) }; + public ITexture getTexture(ForgeDirection side) { + ITexture texture = super.getTexture(side); + if (mMode != 0 && side == facing) { + if (mMode == getModeOrdinal(ITEM_IN)) { + return TextureFactory.of( + texture, + TextureFactory.of(OVERLAY_PIPE_IN), + TextureFactory.of(ITEM_IN_SIGN), + getCoverTexture((byte) side.ordinal())); + } + if (mMode == getModeOrdinal(ITEM_OUT)) { + return TextureFactory.of( + texture, + TextureFactory.of(OVERLAY_PIPE_OUT), + TextureFactory.of(ITEM_OUT_SIGN), + getCoverTexture((byte) side.ordinal())); + } + if (mMode == getModeOrdinal(FLUID_IN)) { + return TextureFactory.of( + texture, + TextureFactory.of(OVERLAY_PIPE_IN), + TextureFactory.of(FLUID_IN_SIGN), + getCoverTexture((byte) side.ordinal())); + } + if (mMode == getModeOrdinal(FLUID_OUT)) { + return TextureFactory.of( + texture, + TextureFactory.of(OVERLAY_PIPE_OUT), + TextureFactory.of(FLUID_OUT_SIGN), + getCoverTexture((byte) side.ordinal())); + } + if (mMode == getModeOrdinal(ENERGY_IN)) { + return TextureFactory + .of(texture, TextureFactory.of(OVERLAY_ENERGY_IN_MULTI), getCoverTexture((byte) side.ordinal())); + } + if (mMode == getModeOrdinal(ENERGY_OUT)) { + return TextureFactory + .of(texture, TextureFactory.of(OVERLAY_ENERGY_OUT_MULTI), getCoverTexture((byte) side.ordinal())); + } } - return new ITexture[] { baseTexture }; + + return TextureFactory.of(texture, getCoverTexture((byte) side.ordinal())); } @Override @@ -398,8 +421,13 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity public boolean onMalletRightClick(EntityPlayer aPlayer, ItemStack tCurrentItem, byte wrenchSide, float aX, float aY, float aZ) { if (mAllowedModes == NOTHING) return true; - + if (mMode == NOTHING) { + facing = ForgeDirection.getOrientation(wrenchSide); + } mMode = getNextAllowedMode(BASIC_MODES); + if (aPlayer.isSneaking()) { + facing = ForgeDirection.getOrientation(wrenchSide); + } GT_Utility.sendChatToPlayer(aPlayer, "Mode set to `" + getModeName(mMode) + "' (" + mMode + ")"); sendClientData((EntityPlayerMP) aPlayer); return true; @@ -430,9 +458,9 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity public int fill(ForgeDirection aDirection, FluidStack aFluidStack, boolean aDoFill) { if (!modeSelected(FLUID_IN)) return 0; final byte aSide = (byte) aDirection.ordinal(); - if (aDirection != ForgeDirection.UNKNOWN - && (aSide != facing || !coverLetsFluidIn(aSide, aFluidStack == null ? null : aFluidStack.getFluid()))) - return 0; + if (aFluidStack == null || isWrongFluid(aFluidStack.getFluid())) return 0; + if (aDirection != ForgeDirection.UNKNOWN && (facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0 + || !coverLetsFluidIn(aSide, aFluidStack.getFluid()))) return 0; final IMultiBlockController controller = getTarget(true); return controller == null ? 0 : controller.fill(this, aDirection, aFluidStack, aDoFill); } @@ -441,9 +469,9 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity public FluidStack drain(ForgeDirection aDirection, FluidStack aFluidStack, boolean aDoDrain) { if (!modeSelected(FLUID_OUT)) return null; final byte aSide = (byte) aDirection.ordinal(); - if (aDirection != ForgeDirection.UNKNOWN - && (aSide != facing || !coverLetsFluidOut(aSide, aFluidStack == null ? null : aFluidStack.getFluid()))) - return null; + if (aFluidStack == null || isWrongFluid(aFluidStack.getFluid())) return null; + if (aDirection != ForgeDirection.UNKNOWN && (facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0 + || !coverLetsFluidOut(aSide, aFluidStack.getFluid()))) return null; final IMultiBlockController controller = getTarget(true); return controller == null ? null : controller.drain(this, aDirection, aFluidStack, aDoDrain); } @@ -454,18 +482,26 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity final byte aSide = (byte) aDirection.ordinal(); final IMultiBlockController controller = getTarget(true); if (controller == null) return null; - final FluidStack aFluidStack = controller.getDrainableFluid(aSide); - if (aDirection != ForgeDirection.UNKNOWN - && (aSide != facing || !coverLetsFluidOut(aSide, aFluidStack == null ? null : aFluidStack.getFluid()))) - return null; - return controller.drain(this, aDirection, aAmountToDrain, aDoDrain); + FluidStack aFluidStack = null; + if (getLockedFluid() != null) { + aFluidStack = controller.getDrainableFluid(aSide, getLockedFluid()); + } else { + aFluidStack = controller.getDrainableFluid(aSide); + } + if (aFluidStack == null || isWrongFluid(aFluidStack.getFluid())) return null; + if (aDirection != ForgeDirection.UNKNOWN && (facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0 + || !coverLetsFluidOut(aSide, aFluidStack.getFluid()))) return null; + return controller.drain(this, aDirection, aFluidStack, aDoDrain); } @Override public boolean canFill(ForgeDirection aDirection, Fluid aFluid) { if (!modeSelected(FLUID_IN)) return false; final byte aSide = (byte) aDirection.ordinal(); - if (aDirection != ForgeDirection.UNKNOWN && (aSide != facing || !coverLetsFluidIn(aSide, aFluid))) return false; + if (aDirection != ForgeDirection.UNKNOWN + && (facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0 || !coverLetsFluidIn(aSide, aFluid))) + return false; + if (isWrongFluid(aFluid)) return false; final IMultiBlockController controller = getTarget(true); return controller != null && controller.canFill(this, aDirection, aFluid); } @@ -474,8 +510,10 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity public boolean canDrain(ForgeDirection aDirection, Fluid aFluid) { if (!modeSelected(FLUID_OUT)) return false; final byte aSide = (byte) aDirection.ordinal(); - if (aDirection != ForgeDirection.UNKNOWN && (aSide != facing || !coverLetsFluidOut(aSide, aFluid))) + if (aDirection != ForgeDirection.UNKNOWN + && (facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0 || !coverLetsFluidOut(aSide, aFluid))) return false; + if (isWrongFluid(aFluid)) return false; final IMultiBlockController controller = getTarget(true); return controller != null && controller.canDrain(this, aDirection, aFluid); } @@ -483,7 +521,8 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity @Override public FluidTankInfo[] getTankInfo(ForgeDirection aDirection) { final byte aSide = (byte) aDirection.ordinal(); - if (!modeSelected(FLUID_IN, FLUID_OUT) || (aSide != SIDE_UNKNOWN && aSide != facing)) + if (!modeSelected(FLUID_IN, FLUID_OUT) + || (aSide != SIDE_UNKNOWN && facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0)) return GT_Values.emptyFluidTankInfo; final IMultiBlockController controller = getTarget(true); if (controller == null) return GT_Values.emptyFluidTankInfo; @@ -500,8 +539,19 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity // #region Energy - Depending on the part type - proxy to the multiblock controller, if we have one @Override - public PowerLogic getPowerLogic(byte side) { + public PowerLogic getPowerLogic(ForgeDirection side) { + if (facing == side) { + return null; + } + + if (!modeSelected(ENERGY_IN, ENERGY_OUT)) { + return null; + } + final IMultiBlockController controller = getTarget(true); + if (controller == null) { + return null; + } return controller.getPowerLogic(this, side); } @@ -548,7 +598,8 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity @Override public int[] getAccessibleSlotsFromSide(int aSide) { - if (!modeSelected(ITEM_IN, ITEM_OUT) || (facing != SIDE_UNKNOWN && facing != aSide)) + if (!modeSelected(ITEM_IN, ITEM_OUT) + || (facing != ForgeDirection.UNKNOWN && facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0)) return GT_Values.emptyIntArray; final IMultiBlockController controller = getTarget(true); return controller != null ? controller.getAccessibleSlotsFromSide(this, (byte) aSide) : GT_Values.emptyIntArray; @@ -556,8 +607,9 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity @Override public boolean canInsertItem(int aSlot, ItemStack aStack, int aSide) { - if (!modeSelected(ITEM_IN, ITEM_OUT) - || (facing != SIDE_UNKNOWN && (facing != aSide || !coverLetsItemsIn((byte) aSide, aSlot)))) return false; + if (!modeSelected(ITEM_IN, ITEM_OUT) || (facing != ForgeDirection.UNKNOWN + && (facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0 || !coverLetsItemsIn((byte) aSide, aSlot)))) + return false; final IMultiBlockController controller = getTarget(true); return (controller != null && controller.canInsertItem(this, aSlot, aStack, (byte) aSide)); } @@ -565,7 +617,9 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity @Override public boolean canExtractItem(int aSlot, ItemStack aStack, int aSide) { if (!modeSelected(ITEM_IN, ITEM_OUT) - || (facing != SIDE_UNKNOWN && (facing != aSide || !coverLetsItemsOut((byte) aSide, aSlot)))) return false; + || (facing != ForgeDirection.UNKNOWN && (facing.compareTo(ForgeDirection.getOrientation(aSide)) != 0 + || !coverLetsItemsOut((byte) aSide, aSlot)))) + return false; final IMultiBlockController controller = getTarget(true); return (controller != null && controller.canExtractItem(this, aSlot, aStack, (byte) aSide)); } @@ -642,10 +696,10 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity @Override public boolean hasGui(byte aSide) { - // UIs only for specific mode(s) - if (modeSelected(ITEM_IN, ITEM_OUT, FLUID_IN, FLUID_OUT)) return true; - - return false; + if (modeSelected(ENERGY_IN, ENERGY_OUT) && facing == ForgeDirection.getOrientation(aSide)) { + return false; + } + return getTarget(true) != null; } protected void addItemInventory(Builder builder, UIBuildContext buildContext) { @@ -670,7 +724,7 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity dropDown.addDropDownItemsSimple( controller.getInventoryNames(this), (buttonWidget, index, label, setSelected) -> buttonWidget.setOnClick((clickData, widget) -> { - if (getNameOfInventoryFromIndex(controller, index).equals("all")) { + if (getNameOfInventoryFromIndex(controller, index).equals(Controller.ALL_INVENTORIES_NAME)) { mLockedInventory = GT_Values.E; mLockedInventoryIndex = 0; } else { @@ -696,11 +750,52 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity return invNames.get(index); } + protected String getNameOfTankArrayFromIndex(final IMultiBlockController controller, int index) { + final List<String> tankNames = controller.getTankArrayIDs(this); + if (index > tankNames.size()) { + return tankNames.get(0); + } + return tankNames.get(index); + } + + protected boolean isWrongFluid(Fluid fluid) { + if (fluid == null) { + return true; + } + Fluid lockedFluid = getLockedFluid(); + if (lockedFluid != null) { + return !fluid.equals(lockedFluid); + } + return false; + } + + protected Fluid getLockedFluid() { + if (configurationTank.get() != null && configurationTank.get() + .getFluid() != null) { + return configurationTank.get() + .getFluid(); + } + return null; + } + protected void addFluidInventory(Builder builder, UIBuildContext buildContext) { final IMultiBlockController controller = getTarget(false); if (controller == null) { return; } + builder.widget( + new DrawableWidget().setDrawable(GT_UITextures.PICTURE_SCREEN_BLACK) + .setPos(7, 4) + .setSize(85, 95)); + if (modeSelected(FLUID_OUT)) { + builder.widget( + new DrawableWidget().setDrawable(GT_UITextures.PICTURE_SCREEN_BLACK) + .setPos(getGUIWidth() - 77, 4) + .setSize(70, 40)) + .widget( + new TextWidget("Locked Fluid").setDefaultColor(COLOR_TEXT_WHITE.get()) + .setPos(getGUIWidth() - 72, 8)); + } final IFluidTank[] tanks = controller.getFluidTanksForGUI(this); final Scrollable scrollable = new Scrollable().setVerticalScroll(); for (int rows = 0; rows * 4 < tanks.length; rows++) { @@ -717,16 +812,47 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity } builder.widget( scrollable.setSize(18 * 4 + 4, 18 * 4) - .setPos(52, 7)); + .setPos(12, 21)); + DropDownWidget dropDown = new DropDownWidget(); + dropDown.addDropDownItemsSimple( + controller.getTankArrayNames(this), + (buttonWidget, index, label, setSelected) -> buttonWidget.setOnClick((clickData, widget) -> { + if (getNameOfTankArrayFromIndex(controller, index).equals(Controller.ALL_INVENTORIES_NAME)) { + mLockedInventory = GT_Values.E; + mLockedInventoryIndex = 0; + } else { + mLockedInventory = getNameOfTankArrayFromIndex(controller, index); + mLockedInventoryIndex = index; + } + setSelected.run(); + }), + true); + builder.widget( + dropDown.setSelected(mLockedInventoryIndex) + .setExpandedMaxHeight(60) + .setDirection(DropDownWidget.Direction.DOWN) + .setPos(13, 8) + .setSize(70, 11)); } @Override public void addUIWidgets(Builder builder, UIBuildContext buildContext) { if (modeSelected(ITEM_IN, ITEM_OUT)) { addItemInventory(builder, buildContext); + return; } if (modeSelected(FLUID_IN, FLUID_OUT)) { addFluidInventory(builder, buildContext); + if (modeSelected(FLUID_OUT)) { + builder.widget( + SlotGroup.ofFluidTanks(Collections.singletonList(configurationTank), 1) + .startFromSlot(0) + .endAtSlot(0) + .phantom(true) + .build() + .setPos(getGUIWidth() - 72, 20)); + } + return; } } @@ -736,15 +862,19 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity issueClientUpdate(); } System.out.println("MultiBlockPart::createWindow"); + if (modeSelected(NOTHING, ENERGY_IN, ENERGY_OUT) || mMode == NOTHING) { + IMultiBlockController controller = getTarget(false); + if (controller == null) { + return super.createWindow(buildContext); + } + return controller.createWindowGUI(buildContext); + } return super.createWindow(buildContext); } @Override protected int getGUIHeight() { - if (modeSelected(ITEM_IN, ITEM_OUT)) { - return super.getGUIHeight() + 11; - } - return super.getGUIHeight(); + return super.getGUIHeight() + 20; } @Override @@ -754,6 +884,11 @@ public abstract class MultiBlockPart extends NonTickableMultiTileEntity new DrawableWidget().setDrawable(getGUITextureSet().getGregTechLogo()) .setSize(17, 17) .setPos(152, 74)); + } else if (modeSelected(FLUID_IN, FLUID_OUT)) { + builder.widget( + new DrawableWidget().setDrawable(getGUITextureSet().getGregTechLogo()) + .setSize(17, 17) + .setPos(152, 82)); } else { super.addGregTechLogo(builder); } diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPowerController.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPowerController.java deleted file mode 100644 index 7f202ec5a4..0000000000 --- a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlockPowerController.java +++ /dev/null @@ -1,44 +0,0 @@ -package gregtech.api.multitileentity.multiblock.base; - -import net.minecraft.nbt.NBTTagCompound; - -import gregtech.api.enums.GT_Values; -import gregtech.api.logic.PowerLogic; -import gregtech.api.logic.interfaces.PowerLogicHost; - -public abstract class MultiBlockPowerController<T extends MultiBlockPowerController<T>> extends MultiBlockController<T> - implements PowerLogicHost { - - public MultiBlockPowerController() { - super(); - power = new PowerLogic().setType(PowerLogic.RECEIVER); - } - - protected PowerLogic power; - - @Override - public void writeMultiTileNBT(NBTTagCompound nbt) { - super.writeMultiTileNBT(nbt); - power.writeToNBT(nbt); - } - - @Override - public void readMultiTileNBT(NBTTagCompound nbt) { - super.readMultiTileNBT(nbt); - power.loadFromNBT(nbt); - } - - @Override - public PowerLogic getPowerLogic(byte side) { - return power; - } - - @Override - public boolean checkMachine() { - boolean result = super.checkMachine(); - power.setEnergyCapacity(GT_Values.V[tier] * 2 * 60 * 20); - power.setAmperage(2); - power.setMaxVoltage(GT_Values.V[tier]); - return result; - } -} diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/PowerController.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/PowerController.java new file mode 100644 index 0000000000..6d58249740 --- /dev/null +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/PowerController.java @@ -0,0 +1,89 @@ +package gregtech.api.multitileentity.multiblock.base; + +import java.util.List; + +import mcp.mobius.waila.api.IWailaConfigHandler; +import mcp.mobius.waila.api.IWailaDataAccessor; + +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.StatCollector; +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; + +import gregtech.api.enums.GT_Values; +import gregtech.api.logic.PowerLogic; +import gregtech.api.logic.interfaces.PowerLogicHost; +import gregtech.api.util.GT_Utility; + +public abstract class PowerController<T extends PowerController<T>> extends Controller<T> implements PowerLogicHost { + + public PowerController() { + super(); + power = new PowerLogic().setType(PowerLogic.RECEIVER); + } + + protected PowerLogic power; + + @Override + public void writeMultiTileNBT(NBTTagCompound nbt) { + super.writeMultiTileNBT(nbt); + power.writeToNBT(nbt); + } + + @Override + public void readMultiTileNBT(NBTTagCompound nbt) { + super.readMultiTileNBT(nbt); + power.loadFromNBT(nbt); + } + + @Override + public PowerLogic getPowerLogic(ForgeDirection side) { + return power; + } + + @Override + public boolean checkMachine() { + boolean result = super.checkMachine(); + power.setEnergyCapacity(GT_Values.V[tier] * 2 * 60 * 20); + power.setAmperage(2); + power.setMaxVoltage(GT_Values.V[tier]); + return result; + } + + @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.setBoolean("isActive", isActive()); + if (isActive()) { + tag.setLong("energyUsage", eut); + } + } + + @Override + public void getWailaBody(ItemStack itemStack, List<String> currentTip, IWailaDataAccessor accessor, + IWailaConfigHandler config) { + super.getWailaBody(itemStack, currentTip, accessor, config); + final NBTTagCompound tag = accessor.getNBTData(); + boolean isActive = tag.getBoolean("isActive"); + if (isActive) { + long actualEnergyUsage = tag.getLong("energyUsage"); + if (actualEnergyUsage > 0) { + currentTip.add( + StatCollector.translateToLocalFormatted( + "GT5U.waila.energy.use", + GT_Utility.formatNumbers(actualEnergyUsage), + GT_Utility.getColoredTierNameFromVoltage(actualEnergyUsage))); + } else if (actualEnergyUsage < 0) { + currentTip.add( + StatCollector.translateToLocalFormatted( + "GT5U.waila.energy.produce", + GT_Utility.formatNumbers(-actualEnergyUsage), + GT_Utility.getColoredTierNameFromVoltage(-actualEnergyUsage))); + } + } + } +} diff --git a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlock_Stackable.java b/src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java index 11d931433f..90a2742290 100644 --- a/src/main/java/gregtech/api/multitileentity/multiblock/base/MultiBlock_Stackable.java +++ b/src/main/java/gregtech/api/multitileentity/multiblock/base/StackableController.java @@ -4,7 +4,7 @@ import net.minecraft.item.ItemStack; import com.gtnewhorizon.structurelib.util.Vec3Impl; -public abstract class MultiBlock_Stackable<T extends MultiBlock_Stackable<T>> extends MultiBlockPowerController<T> { +public abstract class StackableController<T extends StackableController<T>> extends PowerController<T> { protected static String STACKABLE_TOP = "STACKABLE_TOP"; protected static String STACKABLE_MIDDLE = "STACKABLE_MIDDLE"; |