From 385a1d29f6421214ed41db457d7e88344fc13652 Mon Sep 17 00:00:00 2001
From: Ben Spiers <ben.spiers22@gmail.com>
Date: Thu, 8 Jan 2015 05:26:53 +0000
Subject: [PATCH] Make a start on the boiler. It's currently causing odd issues
 with the heat simulator and has various issues such as balance not existing
 yet. Also improve the specification of a load of the multiblock generics to
 reduce needless casting.

---
 src/main/java/mekanism/common/Mekanism.java   |   4 +
 .../mekanism/common/block/BlockBasic.java     |  18 +-
 .../common/content/boiler/BoilerCache.java    |  80 ++-
 .../content/boiler/BoilerSteamTank.java       |  25 +
 .../common/content/boiler/BoilerTank.java     | 198 +++++++
 .../content/boiler/BoilerUpdateProtocol.java  | 109 +++-
 .../content/boiler/BoilerWaterTank.java       |  25 +
 .../boiler/SynchronizedBoilerData.java        | 131 ++++-
 .../matrix/SynchronizedMatrixData.java        |   2 +-
 .../common/multiblock/IMultiblock.java        |   4 +-
 .../common/multiblock/MultiblockCache.java    |   2 +-
 .../common/multiblock/MultiblockManager.java  |   4 +-
 .../common/multiblock/SynchronizedData.java   |   5 +-
 .../common/multiblock/UpdateProtocol.java     |  16 +-
 .../common/tile/TileEntityBoiler.java         | 554 ++++++++++++++++++
 .../common/tile/TileEntityBoilerValve.java    |  74 +++
 .../common/tile/TileEntityDynamicTank.java    |   4 +-
 .../common/tile/TileEntityDynamicValve.java   |   6 +-
 .../common/tile/TileEntityMultiblock.java     |   8 +-
 .../java/mekanism/common/util/HeatUtils.java  |   3 +-
 .../common/tile/TileEntityHeatGenerator.java  |   2 +-
 .../textures/blocks/ctm/BoilerValve-ctm.png   | Bin 0 -> 1414 bytes
 .../textures/blocks/ctm/BoilerValve.png       | Bin 0 -> 1288 bytes
 .../textures/blocks/ctm/SteamBoiler-ctm.png   | Bin 0 -> 1277 bytes
 .../textures/blocks/ctm/SteamBoiler.png       | Bin 0 -> 1202 bytes
 25 files changed, 1236 insertions(+), 38 deletions(-)
 create mode 100644 src/main/java/mekanism/common/content/boiler/BoilerSteamTank.java
 create mode 100644 src/main/java/mekanism/common/content/boiler/BoilerTank.java
 create mode 100644 src/main/java/mekanism/common/content/boiler/BoilerWaterTank.java
 create mode 100644 src/main/java/mekanism/common/tile/TileEntityBoiler.java
 create mode 100644 src/main/java/mekanism/common/tile/TileEntityBoilerValve.java
 create mode 100644 src/main/resources/assets/mekanism/textures/blocks/ctm/BoilerValve-ctm.png
 create mode 100644 src/main/resources/assets/mekanism/textures/blocks/ctm/BoilerValve.png
 create mode 100644 src/main/resources/assets/mekanism/textures/blocks/ctm/SteamBoiler-ctm.png
 create mode 100644 src/main/resources/assets/mekanism/textures/blocks/ctm/SteamBoiler.png

diff --git a/src/main/java/mekanism/common/Mekanism.java b/src/main/java/mekanism/common/Mekanism.java
index 67290da21..5403697de 100644
--- a/src/main/java/mekanism/common/Mekanism.java
+++ b/src/main/java/mekanism/common/Mekanism.java
@@ -62,6 +62,8 @@ import mekanism.common.recipe.inputs.ItemStackInput;
 import mekanism.common.recipe.machines.SmeltingRecipe;
 import mekanism.common.recipe.outputs.ItemStackOutput;
 import mekanism.common.tile.TileEntityAdvancedBoundingBlock;
+import mekanism.common.tile.TileEntityBoiler;
+import mekanism.common.tile.TileEntityBoilerValve;
 import mekanism.common.tile.TileEntityBoundingBlock;
 import mekanism.common.tile.TileEntityCardboardBox;
 import mekanism.common.tile.TileEntityElectricBlock;
@@ -950,6 +952,8 @@ public class Mekanism
 		GameRegistry.registerTileEntity(TileEntitySalinationValve.class, "SalinationValve");
 		GameRegistry.registerTileEntity(TileEntitySalinationBlock.class, "SalinationTank");
 		GameRegistry.registerTileEntity(TileEntityEntangledBlock.class, "EntangledBlock");
+		GameRegistry.registerTileEntity(TileEntityBoiler.class, "SteamBoiler");
+		GameRegistry.registerTileEntity(TileEntityBoilerValve.class, "BoilerValve");
 
 		//Load tile entities that have special renderers.
 		proxy.registerSpecialTileEntities();
diff --git a/src/main/java/mekanism/common/block/BlockBasic.java b/src/main/java/mekanism/common/block/BlockBasic.java
index 18798a2ef..ed5485773 100644
--- a/src/main/java/mekanism/common/block/BlockBasic.java
+++ b/src/main/java/mekanism/common/block/BlockBasic.java
@@ -20,6 +20,8 @@ import mekanism.common.inventory.InventoryBin;
 import mekanism.common.network.PacketTileEntity.TileEntityMessage;
 import mekanism.common.tile.TileEntityBasicBlock;
 import mekanism.common.tile.TileEntityBin;
+import mekanism.common.tile.TileEntityBoiler;
+import mekanism.common.tile.TileEntityBoilerValve;
 import mekanism.common.tile.TileEntityDynamicTank;
 import mekanism.common.tile.TileEntityDynamicValve;
 import mekanism.common.tile.TileEntitySalinationBlock;
@@ -71,6 +73,8 @@ import cpw.mods.fml.relauncher.SideOnly;
  * 0:14: Salination Controller
  * 0:15: Salination Valve
  * 1:0: Salination Block
+ * 1:1: Steam Boiler
+ * 1:2: Boiler Valve
  * @author AidanBrady
  *
  */
@@ -150,8 +154,12 @@ public class BlockBasic extends Block implements IBlockCTM
 				break;
 			case BASIC_BLOCK_2:
 				ctms[0][0] = new CTMData("ctm/SalinationBlock", this, Arrays.asList(0)).addOtherBlockConnectivities(MekanismBlocks.BasicBlock, Arrays.asList(14, 15)).registerIcons(register);
+				ctms[1][0] = new CTMData("ctm/SteamBoiler", this, Arrays.asList(1, 2)).registerIcons(register);
+				ctms[2][0] = new CTMData("ctm/BoilerValve", this, Arrays.asList(2, 1)).registerIcons(register);
 
 				icons[0][0] = ctms[0][0].mainTextureData.icon;
+				icons[1][0] = ctms[1][0].mainTextureData.icon;
+				icons[2][0] = ctms[2][0].mainTextureData.icon;
 				break;
 		}
 	}
@@ -265,7 +273,7 @@ public class BlockBasic extends Block implements IBlockCTM
 				}
 				break;
 			case BASIC_BLOCK_2:
-				for(int i = 0; i < 1; i++)
+				for(int i = 0; i < 3; i++)
 				{
 					list.add(new ItemStack(item, 1, i));
 				}
@@ -642,6 +650,8 @@ public class BlockBasic extends Block implements IBlockCTM
 				switch(metadata)
 				{
 					case 0:
+					case 1:
+					case 2:
 						return true;
 					default:
 						return false;
@@ -679,6 +689,10 @@ public class BlockBasic extends Block implements IBlockCTM
 				{
 					case 0:
 						return new TileEntitySalinationBlock();
+					case 1:
+						return new TileEntityBoiler();
+					case 2:
+						return new TileEntityBoilerValve();
 					default:
 						return null;
 				}
@@ -745,7 +759,7 @@ public class BlockBasic extends Block implements IBlockCTM
 	}
 
 	@Override
-	public ItemStack getPickBlock(MovingObjectPosition target, World world, int x, int y, int z)
+	public ItemStack getPickBlock(MovingObjectPosition target, World world, int x, int y, int z, EntityPlayer player)
 	{
 		ItemStack ret = new ItemStack(this, 1, world.getBlockMetadata(x, y, z));
 
diff --git a/src/main/java/mekanism/common/content/boiler/BoilerCache.java b/src/main/java/mekanism/common/content/boiler/BoilerCache.java
index 3cb9c3db4..1f8ad6606 100644
--- a/src/main/java/mekanism/common/content/boiler/BoilerCache.java
+++ b/src/main/java/mekanism/common/content/boiler/BoilerCache.java
@@ -1,32 +1,98 @@
 package mekanism.common.content.boiler;
 
+import mekanism.common.content.tank.SynchronizedTankData;
 import mekanism.common.multiblock.MultiblockCache;
+import mekanism.common.util.FluidContainerUtils.ContainerEditMode;
 
+import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
+import net.minecraftforge.common.util.Constants.NBT;
+import net.minecraftforge.fluids.FluidStack;
 
 public class BoilerCache extends MultiblockCache<SynchronizedBoilerData>
 {
+	public ItemStack[] inventory = new ItemStack[2];
+
+	public FluidStack water;
+	public FluidStack steam;
+
+	public ContainerEditMode editMode = ContainerEditMode.BOTH;
+
 	@Override
-	public void apply(SynchronizedBoilerData data) 
+	public void apply(SynchronizedBoilerData data)
 	{
-		
+		data.inventory = inventory;
+		data.waterStored = water;
+		data.steamStored = steam;
+		data.editMode = editMode;
 	}
 
 	@Override
-	public void sync(SynchronizedBoilerData data) 
+	public void sync(SynchronizedBoilerData data)
 	{
-		
+		inventory = data.inventory;
+		water = data.waterStored;
+		steam = data.steamStored;
+		editMode = data.editMode;
 	}
 
 	@Override
 	public void load(NBTTagCompound nbtTags)
 	{
-		
+		editMode = ContainerEditMode.values()[nbtTags.getInteger("editMode")];
+
+		NBTTagList tagList = nbtTags.getTagList("Items", NBT.TAG_COMPOUND);
+		inventory = new ItemStack[2];
+
+		for(int tagCount = 0; tagCount < tagList.tagCount(); tagCount++)
+		{
+			NBTTagCompound tagCompound = (NBTTagCompound)tagList.getCompoundTagAt(tagCount);
+			byte slotID = tagCompound.getByte("Slot");
+
+			if(slotID >= 0 && slotID < 2)
+			{
+				inventory[slotID] = ItemStack.loadItemStackFromNBT(tagCompound);
+			}
+		}
+
+		if(nbtTags.hasKey("cachedWater"))
+		{
+			water = FluidStack.loadFluidStackFromNBT(nbtTags.getCompoundTag("cachedWater"));
+		}
+		if(nbtTags.hasKey("cachedSteam"))
+		{
+			steam = FluidStack.loadFluidStackFromNBT(nbtTags.getCompoundTag("cachedSteam"));
+		}
 	}
 
 	@Override
-	public void save(NBTTagCompound nbtTags) 
+	public void save(NBTTagCompound nbtTags)
 	{
-		
+		nbtTags.setInteger("editMode", editMode.ordinal());
+
+		NBTTagList tagList = new NBTTagList();
+
+		for(int slotCount = 0; slotCount < 2; slotCount++)
+		{
+			if(inventory[slotCount] != null)
+			{
+				NBTTagCompound tagCompound = new NBTTagCompound();
+				tagCompound.setByte("Slot", (byte)slotCount);
+				inventory[slotCount].writeToNBT(tagCompound);
+				tagList.appendTag(tagCompound);
+			}
+		}
+
+		nbtTags.setTag("Items", tagList);
+
+		if(water != null)
+		{
+			nbtTags.setTag("cachedWater", water.writeToNBT(new NBTTagCompound()));
+		}
+		if(steam != null)
+		{
+			nbtTags.setTag("cachedSteam", steam.writeToNBT(new NBTTagCompound()));
+		}
 	}
 }
diff --git a/src/main/java/mekanism/common/content/boiler/BoilerSteamTank.java b/src/main/java/mekanism/common/content/boiler/BoilerSteamTank.java
new file mode 100644
index 000000000..a2c554440
--- /dev/null
+++ b/src/main/java/mekanism/common/content/boiler/BoilerSteamTank.java
@@ -0,0 +1,25 @@
+package mekanism.common.content.boiler;
+
+import mekanism.common.tile.TileEntityBoiler;
+
+import net.minecraftforge.fluids.FluidStack;
+
+public class BoilerSteamTank extends BoilerTank
+{
+	public BoilerSteamTank(TileEntityBoiler tileEntity)
+	{
+		super(tileEntity);
+	}
+
+	@Override
+	public FluidStack getFluid()
+	{
+		return steamBoiler.structure != null ? steamBoiler.structure.steamStored : null;
+	}
+
+	public void setFluid(FluidStack stack)
+	{
+		steamBoiler.structure.steamStored = stack;
+	}
+
+}
diff --git a/src/main/java/mekanism/common/content/boiler/BoilerTank.java b/src/main/java/mekanism/common/content/boiler/BoilerTank.java
new file mode 100644
index 000000000..c4e60e754
--- /dev/null
+++ b/src/main/java/mekanism/common/content/boiler/BoilerTank.java
@@ -0,0 +1,198 @@
+package mekanism.common.content.boiler;
+
+import mekanism.api.Coord4D;
+import mekanism.common.content.boiler.SynchronizedBoilerData.ValveData;
+import mekanism.common.tile.TileEntityBoiler;
+import mekanism.common.util.MekanismUtils;
+
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.FluidTankInfo;
+import net.minecraftforge.fluids.IFluidTank;
+
+public abstract class BoilerTank implements IFluidTank
+{
+	public TileEntityBoiler steamBoiler;
+
+	public BoilerTank(TileEntityBoiler tileEntity)
+	{
+		steamBoiler = tileEntity;
+	}
+
+	public abstract void setFluid(FluidStack stack);
+
+	@Override
+	public int getCapacity()
+	{
+		return steamBoiler.structure != null ? steamBoiler.structure.volume * BoilerUpdateProtocol.WATER_PER_TANK : 0;
+	}
+
+	@Override
+	public int fill(FluidStack resource, boolean doFill)
+	{
+		if(steamBoiler.structure != null && !steamBoiler.getWorldObj().isRemote)
+		{
+			if(resource == null || resource.fluidID <= 0)
+			{
+				return 0;
+			}
+
+			if(getFluid() == null || getFluid().fluidID <= 0)
+			{
+				if(resource.amount <= getCapacity())
+				{
+					if(doFill)
+					{
+						setFluid(resource.copy());
+					}
+
+					if(resource.amount > 0 && doFill)
+					{
+						MekanismUtils.saveChunk(steamBoiler);
+						updateValveData(true);
+						steamBoiler.sendPacketToRenderer();
+						updateValveData(false);
+					}
+
+					return resource.amount;
+				}
+				else {
+					if(doFill)
+					{
+						setFluid(resource.copy());
+						getFluid().amount = getCapacity();
+					}
+
+					if(getCapacity() > 0 && doFill)
+					{
+						MekanismUtils.saveChunk(steamBoiler);
+						updateValveData(true);
+						steamBoiler.sendPacketToRenderer();
+						updateValveData(false);
+					}
+
+					return getCapacity();
+				}
+			}
+
+			if(!getFluid().isFluidEqual(resource))
+			{
+				return 0;
+			}
+
+			int space = getCapacity() - getFluid().amount;
+
+			if(resource.amount <= space)
+			{
+				if(doFill)
+				{
+					getFluid().amount += resource.amount;
+				}
+
+				if(resource.amount > 0 && doFill)
+				{
+					MekanismUtils.saveChunk(steamBoiler);
+					updateValveData(true);
+					steamBoiler.sendPacketToRenderer();
+					updateValveData(false);
+				}
+
+				return resource.amount;
+			}
+			else {
+				if(doFill)
+				{
+					getFluid().amount = getCapacity();
+				}
+
+				if(space > 0 && doFill)
+				{
+					MekanismUtils.saveChunk(steamBoiler);
+					updateValveData(true);
+					steamBoiler.sendPacketToRenderer();
+					updateValveData(false);
+				}
+
+				return space;
+			}
+		}
+
+		return 0;
+	}
+
+	public void updateValveData(boolean value)
+	{
+		if(steamBoiler.structure != null)
+		{
+			for(ValveData data : steamBoiler.structure.valves)
+			{
+				if(data.location.equals(Coord4D.get(steamBoiler)))
+				{
+					data.serverFluid = value;
+				}
+			}
+		}
+	}
+
+	@Override
+	public FluidStack drain(int maxDrain, boolean doDrain)
+	{
+		if(steamBoiler.structure != null && !steamBoiler.getWorldObj().isRemote)
+		{
+			if(getFluid() == null || getFluid().fluidID <= 0)
+			{
+				return null;
+			}
+
+			if(getFluid().amount <= 0)
+			{
+				return null;
+			}
+
+			int used = maxDrain;
+
+			if(getFluid().amount < used)
+			{
+				used = getFluid().amount;
+			}
+
+			if(doDrain)
+			{
+				getFluid().amount -= used;
+			}
+
+			FluidStack drained = new FluidStack(getFluid().fluidID, used);
+
+			if(getFluid().amount <= 0)
+			{
+				setFluid(null);
+			}
+
+			if(drained.amount > 0 && doDrain)
+			{
+				MekanismUtils.saveChunk(steamBoiler);
+				steamBoiler.sendPacketToRenderer();
+			}
+
+			return drained;
+		}
+
+		return null;
+	}
+
+	@Override
+	public int getFluidAmount()
+	{
+		if(steamBoiler.structure != null)
+		{
+			return getFluid().amount;
+		}
+
+		return 0;
+	}
+
+	@Override
+	public FluidTankInfo getInfo()
+	{
+		return new FluidTankInfo(this);
+	}
+}
diff --git a/src/main/java/mekanism/common/content/boiler/BoilerUpdateProtocol.java b/src/main/java/mekanism/common/content/boiler/BoilerUpdateProtocol.java
index 85384343d..eeb0e1078 100644
--- a/src/main/java/mekanism/common/content/boiler/BoilerUpdateProtocol.java
+++ b/src/main/java/mekanism/common/content/boiler/BoilerUpdateProtocol.java
@@ -1,6 +1,111 @@
 package mekanism.common.content.boiler;
 
-public class BoilerUpdateProtocol 
-{
+import java.util.List;
 
+import mekanism.api.Coord4D;
+import mekanism.api.util.StackUtils;
+import mekanism.common.Mekanism;
+import mekanism.common.MekanismBlocks;
+import mekanism.common.content.boiler.SynchronizedBoilerData.ValveData;
+import mekanism.common.multiblock.MultiblockCache;
+import mekanism.common.multiblock.MultiblockManager;
+import mekanism.common.multiblock.UpdateProtocol;
+import mekanism.common.tile.TileEntityBoiler;
+import mekanism.common.tile.TileEntityBoilerValve;
+
+import net.minecraft.item.ItemStack;
+
+public class BoilerUpdateProtocol extends UpdateProtocol<SynchronizedBoilerData>
+{
+	public static final int WATER_PER_TANK = 16000;
+	public static final int STEAM_PER_TANK = 160000;
+
+	public BoilerUpdateProtocol(TileEntityBoiler tileEntity)
+	{
+		super(tileEntity);
+	}
+
+	@Override
+	protected boolean isValidFrame(int x, int y, int z)
+	{
+		return pointer.getWorldObj().getBlock(x, y, z) == MekanismBlocks.BasicBlock2 && pointer.getWorldObj().getBlockMetadata(x, y, z) == 1;
+	}
+
+	@Override
+	protected BoilerCache getNewCache()
+	{
+		return new BoilerCache();
+	}
+
+	@Override
+	protected SynchronizedBoilerData getNewStructure()
+	{
+		return new SynchronizedBoilerData();
+	}
+
+	@Override
+	protected MultiblockManager<SynchronizedBoilerData> getManager()
+	{
+		return Mekanism.boilerManager;
+	}
+
+	@Override
+	protected void mergeCaches(List<ItemStack> rejectedItems, MultiblockCache<SynchronizedBoilerData> cache, MultiblockCache<SynchronizedBoilerData> merge)
+	{
+		if(((BoilerCache)cache).water == null)
+		{
+			((BoilerCache)cache).water = ((BoilerCache)merge).water;
+		}
+		else if(((BoilerCache)merge).water != null && ((BoilerCache)cache).water.isFluidEqual(((BoilerCache)merge).water))
+		{
+			((BoilerCache)cache).water.amount += ((BoilerCache)merge).water.amount;
+		}
+
+		if(((BoilerCache)cache).steam == null)
+		{
+			((BoilerCache)cache).steam = ((BoilerCache)merge).steam;
+		}
+		else if(((BoilerCache)merge).steam != null && ((BoilerCache)cache).steam.isFluidEqual(((BoilerCache)merge).steam))
+		{
+			((BoilerCache)cache).steam.amount += ((BoilerCache)merge).steam.amount;
+		}
+
+		List<ItemStack> rejects = StackUtils.getMergeRejects(((BoilerCache)cache).inventory, ((BoilerCache)merge).inventory);
+
+		if(!rejects.isEmpty())
+		{
+			rejectedItems.addAll(rejects);
+		}
+
+		StackUtils.merge(((BoilerCache)cache).inventory, ((BoilerCache)merge).inventory);
+	}
+
+	@Override
+	protected void onFormed()
+	{
+		if((structureFound).waterStored != null)
+		{
+			(structureFound).waterStored.amount = Math.min((structureFound).waterStored.amount, structureFound.volume*WATER_PER_TANK);
+		}
+		if((structureFound).steamStored != null)
+		{
+			(structureFound).steamStored.amount = Math.min((structureFound).waterStored.amount, structureFound.volume*STEAM_PER_TANK);
+		}
+	}
+
+	@Override
+	protected void onStructureCreated(SynchronizedBoilerData structure, int origX, int origY, int origZ, int xmin, int xmax, int ymin, int ymax, int zmin, int zmax)
+	{
+		for(Coord4D obj : structure.locations)
+		{
+			if(obj.getTileEntity(pointer.getWorldObj()) instanceof TileEntityBoilerValve)
+			{
+				ValveData data = new ValveData();
+				data.location = obj;
+				data.side = getSide(obj, origX+xmin, origX+xmax, origY+ymin, origY+ymax, origZ+zmin, origZ+zmax);
+
+				((SynchronizedBoilerData)structure).valves.add(data);
+			}
+		}
+	}
 }
diff --git a/src/main/java/mekanism/common/content/boiler/BoilerWaterTank.java b/src/main/java/mekanism/common/content/boiler/BoilerWaterTank.java
new file mode 100644
index 000000000..bbe231061
--- /dev/null
+++ b/src/main/java/mekanism/common/content/boiler/BoilerWaterTank.java
@@ -0,0 +1,25 @@
+package mekanism.common.content.boiler;
+
+import mekanism.common.tile.TileEntityBoiler;
+
+import net.minecraftforge.fluids.FluidStack;
+
+public class BoilerWaterTank extends BoilerTank
+{
+	public BoilerWaterTank(TileEntityBoiler tileEntity)
+	{
+		super(tileEntity);
+	}
+
+	@Override
+	public FluidStack getFluid()
+	{
+		return steamBoiler.structure != null ? steamBoiler.structure.waterStored : null;
+	}
+
+	public void setFluid(FluidStack stack)
+	{
+		steamBoiler.structure.waterStored = stack;
+	}
+
+}
diff --git a/src/main/java/mekanism/common/content/boiler/SynchronizedBoilerData.java b/src/main/java/mekanism/common/content/boiler/SynchronizedBoilerData.java
index 93eafdfe7..61335983f 100644
--- a/src/main/java/mekanism/common/content/boiler/SynchronizedBoilerData.java
+++ b/src/main/java/mekanism/common/content/boiler/SynchronizedBoilerData.java
@@ -1,8 +1,137 @@
 package mekanism.common.content.boiler;
 
+import java.util.HashSet;
+import java.util.Set;
+
+import mekanism.api.Coord4D;
+import mekanism.api.IHeatTransfer;
 import mekanism.common.multiblock.SynchronizedData;
+import mekanism.common.util.FluidContainerUtils.ContainerEditMode;
 
-public class SynchronizedBoilerData extends SynchronizedData<SynchronizedBoilerData>
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.FluidRegistry;
+import net.minecraftforge.fluids.FluidStack;
+
+public class SynchronizedBoilerData extends SynchronizedData<SynchronizedBoilerData> implements IHeatTransfer
 {
+	public FluidStack waterStored;
 
+	public FluidStack steamStored;
+
+	public double temperature;
+
+	public double heatToAbsorb;
+
+	public double heatCapacity = 100;
+
+	public double enthalpyOfVaporization = 10;
+
+	public ContainerEditMode editMode = ContainerEditMode.BOTH;
+
+	public ItemStack[] inventory = new ItemStack[2];
+
+	public Set<ValveData> valves = new HashSet<ValveData>();
+
+	@Override
+	public ItemStack[] getInventory()
+	{
+		return inventory;
+	}
+
+	@Override
+	public double getTemp()
+	{
+		return temperature;
+	}
+
+	@Override
+	public double getInverseConductionCoefficient()
+	{
+		return 100;
+	}
+
+	@Override
+	public double getInsulationCoefficient(ForgeDirection side)
+	{
+		return 100;
+	}
+
+	@Override
+	public void transferHeatTo(double heat)
+	{
+		heatToAbsorb = heat;
+	}
+
+	@Override
+	public double[] simulateHeat()
+	{
+		return new double[0];
+	}
+
+	@Override
+	public double applyTemperatureChange()
+	{
+
+		if(temperature < 100 + IHeatTransfer.AMBIENT_TEMP)
+		{
+			double temperatureDeficit = 100 + IHeatTransfer.AMBIENT_TEMP - temperature;
+			double heatNeeded = temperatureDeficit * volume * heatCapacity * 16;
+			double heatProvided = Math.min(heatToAbsorb, heatNeeded);
+			heatToAbsorb -= heatProvided;
+			temperature += heatProvided / (volume * heatCapacity * 16);
+		}
+		if(temperature >= 100 + IHeatTransfer.AMBIENT_TEMP && waterStored != null)
+		{
+			int amountToBoil = (int)Math.floor(heatToAbsorb / enthalpyOfVaporization);
+			amountToBoil = Math.min(amountToBoil, waterStored.amount);
+			waterStored.amount -= amountToBoil;
+			if(steamStored == null)
+			{
+				steamStored = new FluidStack(FluidRegistry.getFluid("steam"), amountToBoil);
+			}
+			else
+			{
+				steamStored.amount += amountToBoil;
+			}
+
+			heatToAbsorb -= amountToBoil * enthalpyOfVaporization;
+		}
+		heatToAbsorb *= 0.2;
+		return temperature;
+	}
+
+	@Override
+	public boolean canConnectHeat(ForgeDirection side)
+	{
+		return false;
+	}
+
+	@Override
+	public IHeatTransfer getAdjacent(ForgeDirection side)
+	{
+		return null;
+	}
+
+	public static class ValveData
+	{
+		public ForgeDirection side;
+		public Coord4D location;
+		public boolean serverFluid;
+
+		@Override
+		public int hashCode()
+		{
+			int code = 1;
+			code = 31 * code + side.ordinal();
+			code = 31 * code + location.hashCode();
+			return code;
+		}
+
+		@Override
+		public boolean equals(Object obj)
+		{
+			return obj instanceof ValveData && ((ValveData)obj).side == side && ((ValveData)obj).location.equals(location);
+		}
+	}
 }
diff --git a/src/main/java/mekanism/common/content/matrix/SynchronizedMatrixData.java b/src/main/java/mekanism/common/content/matrix/SynchronizedMatrixData.java
index a786867f4..3112707b1 100644
--- a/src/main/java/mekanism/common/content/matrix/SynchronizedMatrixData.java
+++ b/src/main/java/mekanism/common/content/matrix/SynchronizedMatrixData.java
@@ -5,7 +5,7 @@ import mekanism.common.multiblock.SynchronizedData;
 
 import net.minecraft.item.ItemStack;
 
-public class SynchronizedMatrixData extends SynchronizedData<SynchronizedTankData>
+public class SynchronizedMatrixData extends SynchronizedData<SynchronizedMatrixData>
 {
 	public ItemStack[] inventory = new ItemStack[2];
 	
diff --git a/src/main/java/mekanism/common/multiblock/IMultiblock.java b/src/main/java/mekanism/common/multiblock/IMultiblock.java
index 1cdbbc52a..dc2ae616b 100644
--- a/src/main/java/mekanism/common/multiblock/IMultiblock.java
+++ b/src/main/java/mekanism/common/multiblock/IMultiblock.java
@@ -1,6 +1,6 @@
 package mekanism.common.multiblock;
 
-public interface IMultiblock<T>
+public interface IMultiblock<T extends SynchronizedData<T>>
 {
-	public SynchronizedData<T> getSynchronizedData();
+	public T getSynchronizedData();
 }
diff --git a/src/main/java/mekanism/common/multiblock/MultiblockCache.java b/src/main/java/mekanism/common/multiblock/MultiblockCache.java
index df3f56388..3836889f8 100644
--- a/src/main/java/mekanism/common/multiblock/MultiblockCache.java
+++ b/src/main/java/mekanism/common/multiblock/MultiblockCache.java
@@ -6,7 +6,7 @@ import mekanism.api.Coord4D;
 
 import net.minecraft.nbt.NBTTagCompound;
 
-public abstract class MultiblockCache<T>
+public abstract class MultiblockCache<T extends SynchronizedData<T>>
 {
 	public HashSet<Coord4D> locations = new HashSet<Coord4D>();
 	
diff --git a/src/main/java/mekanism/common/multiblock/MultiblockManager.java b/src/main/java/mekanism/common/multiblock/MultiblockManager.java
index fa4e30075..c94f2e2cf 100644
--- a/src/main/java/mekanism/common/multiblock/MultiblockManager.java
+++ b/src/main/java/mekanism/common/multiblock/MultiblockManager.java
@@ -16,7 +16,7 @@ import net.minecraft.world.World;
 import net.minecraft.world.WorldSavedData;
 import net.minecraftforge.common.util.Constants.NBT;
 
-public class MultiblockManager<T>
+public class MultiblockManager<T extends SynchronizedData<T>>
 {
 	private static Set<MultiblockManager> managers = new HashSet<MultiblockManager>();
 	
@@ -80,8 +80,6 @@ public class MultiblockManager<T>
 
 	/**
 	 * Updates a dynamic tank cache with the defined inventory ID with the parameterized values.
-	 * @param inventoryID - inventory ID of the dynamic tank
-	 * @param cache - cache of the dynamic tank
 	 * @param multiblock - dynamic tank TileEntity
 	 */
 	public void updateCache(IMultiblock<T> multiblock)
diff --git a/src/main/java/mekanism/common/multiblock/SynchronizedData.java b/src/main/java/mekanism/common/multiblock/SynchronizedData.java
index f3ffed487..cab4f5e3f 100644
--- a/src/main/java/mekanism/common/multiblock/SynchronizedData.java
+++ b/src/main/java/mekanism/common/multiblock/SynchronizedData.java
@@ -7,7 +7,7 @@ import mekanism.api.Coord4D;
 
 import net.minecraft.item.ItemStack;
 
-public abstract class SynchronizedData<T>
+public abstract class SynchronizedData<T extends SynchronizedData<T>>
 {
 	public Set<Coord4D> locations = new HashSet<Coord4D>();
 
@@ -26,6 +26,9 @@ public abstract class SynchronizedData<T>
 	public boolean hasRenderer;
 
 	public Coord4D renderLocation;
+
+	public Coord4D minLocation;
+	public Coord4D maxLocation;
 	
 	public ItemStack[] getInventory()
 	{
diff --git a/src/main/java/mekanism/common/multiblock/UpdateProtocol.java b/src/main/java/mekanism/common/multiblock/UpdateProtocol.java
index 5b8770d70..a7b0a86bb 100644
--- a/src/main/java/mekanism/common/multiblock/UpdateProtocol.java
+++ b/src/main/java/mekanism/common/multiblock/UpdateProtocol.java
@@ -14,13 +14,13 @@ import net.minecraft.tileentity.TileEntity;
 import net.minecraft.world.World;
 import net.minecraftforge.common.util.ForgeDirection;
 
-public abstract class UpdateProtocol<T>
+public abstract class UpdateProtocol<T extends SynchronizedData<T>>
 {
 	/** The multiblock nodes that have already been iterated over. */
 	public Set<TileEntityMultiblock<T>> iteratedNodes = new HashSet<TileEntityMultiblock<T>>();
 
 	/** The structures found, all connected by some nodes to the pointer. */
-	public SynchronizedData<T> structureFound = null;
+	public T structureFound = null;
 
 	/** The original block the calculation is getting run from. */
 	public TileEntityMultiblock<T> pointer;
@@ -178,13 +178,15 @@ public abstract class UpdateProtocol<T>
 		{
 			if(rightBlocks && rightFrame && isHollow && isCorner)
 			{
-				SynchronizedData<T> structure = getNewStructure();
+				T structure = getNewStructure();
 				structure.locations = locations;
 				structure.volLength = Math.abs(xmax-xmin)+1;
 				structure.volHeight = Math.abs(ymax-ymin)+1;
 				structure.volWidth = Math.abs(zmax-zmin)+1;
 				structure.volume = volume;
 				structure.renderLocation = Coord4D.get(tile).translate(0, 1, 0);
+				structure.minLocation = Coord4D.get(tile).translate(xmin, ymin, zmin);
+				structure.maxLocation = Coord4D.get(tile).translate(xmax, ymax, zmax);
 				
 				onStructureCreated(structure, origX, origY, origZ, xmin, xmax, ymin, ymax, zmin, zmax);
 
@@ -346,7 +348,7 @@ public abstract class UpdateProtocol<T>
 	
 	protected abstract MultiblockCache<T> getNewCache();
 	
-	protected abstract SynchronizedData<T> getNewStructure();
+	protected abstract T getNewStructure();
 	
 	protected abstract MultiblockManager<T> getManager();
 	
@@ -354,7 +356,7 @@ public abstract class UpdateProtocol<T>
 	
 	protected void onFormed() {}
 	
-	protected void onStructureCreated(SynchronizedData<T> structure, int origX, int origY, int origZ, int xmin, int xmax, int ymin, int ymax, int zmin, int zmax) {}
+	protected void onStructureCreated(T structure, int origX, int origY, int origZ, int xmin, int xmax, int ymin, int ymax, int zmin, int zmax) {}
 
 	/**
 	 * Runs the protocol and updates all tanks that make a part of the dynamic tank.
@@ -371,7 +373,7 @@ public abstract class UpdateProtocol<T>
 				{
 					for(TileEntity tile : iteratedNodes)
 					{
-						((TileEntityMultiblock<T>)tileEntity).structure = null;
+						((TileEntityMultiblock<T>)tile).structure = null;
 					}
 
 					return;
@@ -406,7 +408,7 @@ public abstract class UpdateProtocol<T>
 							cache = (MultiblockCache<T>)Mekanism.tankManager.pullInventory(pointer.getWorldObj(), id);
 						}
 						else {
-							mergeCaches(rejectedItems, cache, (MultiblockCache<T>)getManager().pullInventory(pointer.getWorldObj(), id));
+							mergeCaches(rejectedItems, cache, getManager().pullInventory(pointer.getWorldObj(), id));
 						}
 						
 						idToUse = id;
diff --git a/src/main/java/mekanism/common/tile/TileEntityBoiler.java b/src/main/java/mekanism/common/tile/TileEntityBoiler.java
new file mode 100644
index 000000000..dd253d1f6
--- /dev/null
+++ b/src/main/java/mekanism/common/tile/TileEntityBoiler.java
@@ -0,0 +1,554 @@
+package mekanism.common.tile;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import mekanism.api.Coord4D;
+import mekanism.api.IHeatTransfer;
+import mekanism.api.Range4D;
+import mekanism.common.Mekanism;
+import mekanism.common.base.IFluidContainerManager;
+import mekanism.common.content.boiler.BoilerUpdateProtocol;
+import mekanism.common.content.boiler.SynchronizedBoilerData;
+import mekanism.common.content.boiler.SynchronizedBoilerData.ValveData;
+import mekanism.common.multiblock.MultiblockManager;
+import mekanism.common.network.PacketTileEntity.TileEntityMessage;
+import mekanism.common.util.FluidContainerUtils;
+import mekanism.common.util.FluidContainerUtils.ContainerEditMode;
+import mekanism.common.util.HeatUtils;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.FluidContainerRegistry;
+import net.minecraftforge.fluids.FluidStack;
+import net.minecraftforge.fluids.IFluidContainerItem;
+
+import io.netty.buffer.ByteBuf;
+
+public class TileEntityBoiler extends TileEntityMultiblock<SynchronizedBoilerData> implements IFluidContainerManager, IHeatTransfer
+{
+	/** A client-sided and server-sided map of valves on this tank's structure, used on the client for rendering fluids. */
+	public Map<ValveData, Integer> valveViewing = new HashMap<ValveData, Integer>();
+
+	/** The capacity this tank has on the client-side. */
+	public int clientWaterCapacity;
+	public int clientSteamCapacity;
+
+	public float prevWaterScale;
+	public float prevSteamScale;
+
+	public ForgeDirection innerSide;
+
+	public double temperature;
+	public double heatToAbsorb;
+	public double invHeatCapacity = 10;
+
+	public TileEntityBoiler()
+	{
+		this("SteamBoiler");
+	}
+
+	public TileEntityBoiler(String name)
+	{
+		super(name);
+		inventory = new ItemStack[2];
+	}
+
+	@Override
+	public void onUpdate()
+	{
+		super.onUpdate();
+
+		if(worldObj.isRemote)
+		{
+			if(structure != null && clientHasStructure && isRendering)
+			{
+				for(ValveData data : valveViewing.keySet())
+				{
+					if(valveViewing.get(data) > 0)
+					{
+						valveViewing.put(data, valveViewing.get(data)-1);
+					}
+				}
+
+				float targetScale = (float)(structure.waterStored != null ? structure.waterStored.amount : 0)/clientWaterCapacity;
+
+				if(Math.abs(prevWaterScale - targetScale) > 0.01)
+				{
+					prevWaterScale = (9*prevWaterScale + targetScale)/10;
+				}
+
+				targetScale = (float)(structure.steamStored != null ? structure.steamStored.amount : 0)/clientSteamCapacity;
+
+				if(Math.abs(prevSteamScale - targetScale) > 0.01)
+				{
+					prevSteamScale = (9*prevSteamScale+ targetScale)/10;
+				}
+			}
+
+			if(!clientHasStructure || !isRendering)
+			{
+				for(ValveData data : valveViewing.keySet())
+				{
+					TileEntityDynamicTank tileEntity = (TileEntityDynamicTank)data.location.getTileEntity(worldObj);
+
+					if(tileEntity != null)
+					{
+						tileEntity.clientHasStructure = false;
+					}
+				}
+
+				valveViewing.clear();
+			}
+		}
+
+		if(!worldObj.isRemote)
+		{
+			if(structure != null)
+			{
+				manageInventory();
+			}
+
+			simulateHeat();
+			applyTemperatureChange();
+			if(structure != null)
+			{
+				structure.applyTemperatureChange();
+			}
+		}
+	}
+
+	public void manageInventory()
+	{
+		int max = structure.volume * BoilerUpdateProtocol.WATER_PER_TANK;
+
+		if(structure.inventory[0] != null)
+		{
+			if(structure.inventory[0].getItem() instanceof IFluidContainerItem)
+			{
+				if(structure.editMode == ContainerEditMode.FILL && structure.waterStored != null)
+				{
+					int prev = structure.waterStored.amount;
+
+					structure.waterStored.amount -= FluidContainerUtils.insertFluid(structure.waterStored, structure.inventory[0]);
+
+					if(prev == structure.waterStored.amount || structure.waterStored.amount == 0)
+					{
+						if(structure.inventory[1] == null)
+						{
+							structure.inventory[1] = structure.inventory[0].copy();
+							structure.inventory[0] = null;
+
+							markDirty();
+						}
+					}
+
+					if(structure.waterStored.amount == 0)
+					{
+						structure.waterStored = null;
+					}
+				}
+				else if(structure.editMode == ContainerEditMode.EMPTY)
+				{
+					if(structure.waterStored != null)
+					{
+						FluidStack received = FluidContainerUtils.extractFluid(max-structure.waterStored.amount, structure.inventory[0], structure.waterStored.getFluid());
+
+						if(received != null)
+						{
+							structure.waterStored.amount += received.amount;
+						}
+					}
+					else {
+						structure.waterStored = FluidContainerUtils.extractFluid(max, structure.inventory[0], null);
+					}
+
+					int newStored = structure.waterStored != null ? structure.waterStored.amount : 0;
+
+					if(((IFluidContainerItem)structure.inventory[0].getItem()).getFluid(structure.inventory[0]) == null || newStored == max)
+					{
+						if(structure.inventory[1] == null)
+						{
+							structure.inventory[1] = structure.inventory[0].copy();
+							structure.inventory[0] = null;
+
+							markDirty();
+						}
+					}
+				}
+			}
+			else if(FluidContainerRegistry.isEmptyContainer(structure.inventory[0]) && (structure.editMode == ContainerEditMode.BOTH || structure.editMode == ContainerEditMode.FILL))
+			{
+				if(structure.waterStored != null && structure.waterStored.amount >= FluidContainerRegistry.BUCKET_VOLUME)
+				{
+					ItemStack filled = FluidContainerRegistry.fillFluidContainer(structure.waterStored, structure.inventory[0]);
+
+					if(filled != null)
+					{
+						if(structure.inventory[1] == null || (structure.inventory[1].isItemEqual(filled) && structure.inventory[1].stackSize+1 <= filled.getMaxStackSize()))
+						{
+							structure.inventory[0].stackSize--;
+
+							if(structure.inventory[0].stackSize <= 0)
+							{
+								structure.inventory[0] = null;
+							}
+
+							if(structure.inventory[1] == null)
+							{
+								structure.inventory[1] = filled;
+							}
+							else {
+								structure.inventory[1].stackSize++;
+							}
+
+							markDirty();
+
+							structure.waterStored.amount -= FluidContainerRegistry.getFluidForFilledItem(filled).amount;
+
+							if(structure.waterStored.amount == 0)
+							{
+								structure.waterStored = null;
+							}
+
+							Mekanism.packetHandler.sendToReceivers(new TileEntityMessage(Coord4D.get(this), getNetworkedData(new ArrayList())), new Range4D(Coord4D.get(this)));
+						}
+					}
+				}
+			}
+			else if(FluidContainerRegistry.isFilledContainer(structure.inventory[0]) && (structure.editMode == ContainerEditMode.BOTH || structure.editMode == ContainerEditMode.EMPTY))
+			{
+				FluidStack itemFluid = FluidContainerRegistry.getFluidForFilledItem(structure.inventory[0]);
+
+				if((structure.waterStored == null && itemFluid.amount <= max) || structure.waterStored.amount+itemFluid.amount <= max)
+				{
+					if(structure.waterStored != null && !structure.waterStored.isFluidEqual(itemFluid))
+					{
+						return;
+					}
+
+					ItemStack containerItem = structure.inventory[0].getItem().getContainerItem(structure.inventory[0]);
+
+					boolean filled = false;
+
+					if(containerItem != null)
+					{
+						if(structure.inventory[1] == null || (structure.inventory[1].isItemEqual(containerItem) && structure.inventory[1].stackSize+1 <= containerItem.getMaxStackSize()))
+						{
+							structure.inventory[0] = null;
+
+							if(structure.inventory[1] == null)
+							{
+								structure.inventory[1] = containerItem;
+							}
+							else {
+								structure.inventory[1].stackSize++;
+							}
+
+							filled = true;
+						}
+					}
+					else {
+						structure.inventory[0].stackSize--;
+
+						if(structure.inventory[0].stackSize == 0)
+						{
+							structure.inventory[0] = null;
+						}
+
+						filled = true;
+					}
+
+					if(filled)
+					{
+						if(structure.waterStored == null)
+						{
+							structure.waterStored = itemFluid.copy();
+						}
+						else {
+							structure.waterStored.amount += itemFluid.amount;
+						}
+
+						markDirty();
+					}
+
+					Mekanism.packetHandler.sendToReceivers(new TileEntityMessage(Coord4D.get(this), getNetworkedData(new ArrayList())), new Range4D(Coord4D.get(this)));
+				}
+			}
+		}
+	}
+
+	@Override
+	protected SynchronizedBoilerData getNewStructure()
+	{
+		return new SynchronizedBoilerData();
+	}
+
+	@Override
+	protected BoilerUpdateProtocol getProtocol()
+	{
+		return new BoilerUpdateProtocol(this);
+	}
+
+	@Override
+	public MultiblockManager<SynchronizedBoilerData> getManager()
+	{
+		return Mekanism.boilerManager;
+	}
+
+	@Override
+	public ArrayList getNetworkedData(ArrayList data)
+	{
+		super.getNetworkedData(data);
+
+		if(structure != null)
+		{
+			data.add(structure.volume*BoilerUpdateProtocol.WATER_PER_TANK);
+			data.add(structure.volume*BoilerUpdateProtocol.STEAM_PER_TANK);
+			data.add(structure.editMode.ordinal());
+		}
+
+		if(structure != null && structure.waterStored != null)
+		{
+			data.add(1);
+			data.add(structure.waterStored.fluidID);
+			data.add(structure.waterStored.amount);
+		}
+		else {
+			data.add(0);
+		}
+
+		if(structure != null && structure.steamStored != null)
+		{
+			data.add(1);
+			data.add(structure.steamStored.fluidID);
+			data.add(structure.steamStored.amount);
+		}
+		else {
+			data.add(0);
+		}
+
+		if(structure != null && isRendering)
+		{
+			data.add(structure.valves.size());
+
+			for(ValveData valveData : structure.valves)
+			{
+				valveData.location.write(data);
+
+				data.add(valveData.side.ordinal());
+				data.add(valveData.serverFluid);
+			}
+		}
+
+		return data;
+	}
+
+	@Override
+	public void handlePacketData(ByteBuf dataStream)
+	{
+		super.handlePacketData(dataStream);
+
+		if(clientHasStructure)
+		{
+			clientWaterCapacity = dataStream.readInt();
+			clientSteamCapacity = dataStream.readInt();
+			structure.editMode = ContainerEditMode.values()[dataStream.readInt()];
+		}
+
+		if(dataStream.readInt() == 1)
+		{
+			structure.waterStored = new FluidStack(dataStream.readInt(), dataStream.readInt());
+		}
+		else {
+			structure.waterStored = null;
+		}
+
+		if(dataStream.readInt() == 1)
+		{
+			structure.steamStored = new FluidStack(dataStream.readInt(), dataStream.readInt());
+		}
+		else {
+			structure.steamStored = null;
+		}
+
+		if(clientHasStructure && isRendering)
+		{
+			int size = dataStream.readInt();
+
+			for(int i = 0; i < size; i++)
+			{
+				ValveData data = new ValveData();
+				data.location = Coord4D.read(dataStream);
+				data.side = ForgeDirection.getOrientation(dataStream.readInt());
+				int viewingTicks = 0;
+
+				if(dataStream.readBoolean())
+				{
+					viewingTicks = 30;
+				}
+
+				if(viewingTicks == 0)
+				{
+					if(valveViewing.containsKey(data) && valveViewing.get(data) > 0)
+					{
+						continue;
+					}
+				}
+
+				valveViewing.put(data, viewingTicks);
+
+				TileEntityBoiler tileEntity = (TileEntityBoiler)data.location.getTileEntity(worldObj);
+
+				if(tileEntity != null)
+				{
+					tileEntity.clientHasStructure = true;
+				}
+			}
+		}
+	}
+
+	public int getScaledWaterLevel(int i)
+	{
+		if(clientWaterCapacity == 0 || structure.waterStored == null)
+		{
+			return 0;
+		}
+
+		return structure.waterStored.amount*i / clientWaterCapacity;
+	}
+
+	public int getScaledSteamLevel(int i)
+	{
+		if(clientSteamCapacity == 0 || structure.steamStored == null)
+		{
+			return 0;
+		}
+
+		return structure.steamStored.amount*i / clientSteamCapacity;
+	}
+
+	@Override
+	public ContainerEditMode getContainerEditMode()
+	{
+		if(structure != null)
+		{
+			return structure.editMode;
+		}
+
+		return ContainerEditMode.BOTH;
+	}
+
+	@Override
+	public void setContainerEditMode(ContainerEditMode mode)
+	{
+		if(structure == null)
+		{
+			return;
+		}
+
+		structure.editMode = mode;
+	}
+
+	@Override
+	public double getTemp()
+	{
+		return temperature;
+	}
+
+	@Override
+	public double getInverseConductionCoefficient()
+	{
+		return 10;
+	}
+
+	@Override
+	public double getInsulationCoefficient(ForgeDirection side)
+	{
+		return 10;
+	}
+
+	@Override
+	public void transferHeatTo(double heat)
+	{
+		heatToAbsorb += heat;
+	}
+
+	@Override
+	public double[] simulateHeat()
+	{
+		innerSide = null;
+		return HeatUtils.simulate(this);
+	}
+
+	@Override
+	public double applyTemperatureChange()
+	{
+		temperature += invHeatCapacity * heatToAbsorb;
+		heatToAbsorb = 0;
+		return temperature;
+	}
+
+	@Override
+	public boolean canConnectHeat(ForgeDirection side)
+	{
+		return structure == null || !isInnerSide(side);
+	}
+
+	@Override
+	public IHeatTransfer getAdjacent(ForgeDirection side)
+	{
+		if(structure != null && isInnerSide(side))
+		{
+			return structure;
+		}
+		else
+		{
+			TileEntity adj = Coord4D.get(this).getFromSide(side).getTileEntity(worldObj);
+			if(adj instanceof IHeatTransfer)
+			{
+				return (IHeatTransfer)adj;
+			}
+		}
+
+		return null;
+	}
+
+	public boolean isInnerSide(ForgeDirection side)
+	{
+		if(innerSide != null)
+		{
+			return side == innerSide;
+		}
+
+		if(!Coord4D.get(this).getFromSide(side).getBlock(worldObj).isAir(worldObj, xCoord, yCoord, zCoord))
+		{
+			return false;
+		}
+
+		if(structure == null || structure.minLocation == null || structure.maxLocation == null)
+		{
+			return false;
+		}
+
+		switch(side)
+		{
+			case DOWN:
+				return yCoord == structure.maxLocation.yCoord;
+			case UP:
+				return yCoord == structure.minLocation.yCoord;
+			case NORTH:
+				return zCoord == structure.maxLocation.zCoord;
+			case SOUTH:
+				return zCoord == structure.minLocation.zCoord;
+			case WEST:
+				return xCoord == structure.maxLocation.xCoord;
+			case EAST:
+				return xCoord == structure.minLocation.xCoord;
+			default:
+				return false;
+		}
+	}
+}
diff --git a/src/main/java/mekanism/common/tile/TileEntityBoilerValve.java b/src/main/java/mekanism/common/tile/TileEntityBoilerValve.java
new file mode 100644
index 000000000..edfcc374e
--- /dev/null
+++ b/src/main/java/mekanism/common/tile/TileEntityBoilerValve.java
@@ -0,0 +1,74 @@
+package mekanism.common.tile;
+
+import mekanism.common.content.boiler.BoilerSteamTank;
+import mekanism.common.content.boiler.BoilerTank;
+import mekanism.common.content.boiler.BoilerWaterTank;
+import mekanism.common.util.PipeUtils;
+
+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.IFluidHandler;
+
+public class TileEntityBoilerValve extends TileEntityBoiler implements IFluidHandler
+{
+	public BoilerTank waterTank;
+	public BoilerTank steamTank;
+
+	public TileEntityBoilerValve()
+	{
+		super("Boiler Valve");
+		waterTank = new BoilerWaterTank(this);
+		steamTank = new BoilerSteamTank(this);
+	}
+
+	@Override
+	public FluidTankInfo[] getTankInfo(ForgeDirection from)
+	{
+		return ((!worldObj.isRemote && structure != null) || (worldObj.isRemote && clientHasStructure)) ? new FluidTankInfo[] {waterTank.getInfo(), steamTank.getInfo()} : PipeUtils.EMPTY;
+	}
+
+	@Override
+	public int fill(ForgeDirection from, FluidStack resource, boolean doFill)
+	{
+		return waterTank.fill(resource, doFill);
+	}
+
+	@Override
+	public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain)
+	{
+		if(structure != null && structure.steamStored != null)
+		{
+			if(resource.getFluid() == structure.steamStored.getFluid())
+			{
+				return steamTank.drain(resource.amount, doDrain);
+			}
+		}
+
+		return null;
+	}
+
+	@Override
+	public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain)
+	{
+		if(structure != null)
+		{
+			return steamTank.drain(maxDrain, doDrain);
+		}
+
+		return null;
+	}
+
+	@Override
+	public boolean canFill(ForgeDirection from, Fluid fluid)
+	{
+		return true;
+	}
+
+	@Override
+	public boolean canDrain(ForgeDirection from, Fluid fluid)
+	{
+		return true;
+	}
+}
diff --git a/src/main/java/mekanism/common/tile/TileEntityDynamicTank.java b/src/main/java/mekanism/common/tile/TileEntityDynamicTank.java
index ddcb2b6c5..030b9ac12 100644
--- a/src/main/java/mekanism/common/tile/TileEntityDynamicTank.java
+++ b/src/main/java/mekanism/common/tile/TileEntityDynamicTank.java
@@ -25,7 +25,7 @@ import net.minecraftforge.fluids.IFluidContainerItem;
 
 import io.netty.buffer.ByteBuf;
 
-public class TileEntityDynamicTank extends TileEntityMultiblock<SynchronizedTankData> implements IFluidContainerManager, IMultiblock<SynchronizedTankData>
+public class TileEntityDynamicTank extends TileEntityMultiblock<SynchronizedTankData> implements IFluidContainerManager
 {
 	/** A client-sided and server-sided map of valves on this tank's structure, used on the client for rendering fluids. */
 	public Map<ValveData, Integer> valveViewing = new HashMap<ValveData, Integer>();
@@ -98,7 +98,7 @@ public class TileEntityDynamicTank extends TileEntityMultiblock<SynchronizedTank
 
 	public void manageInventory()
 	{
-		int max = structure.volume*TankUpdateProtocol.FLUID_PER_TANK;
+		int max = structure.volume * TankUpdateProtocol.FLUID_PER_TANK;
 
 		if(structure.inventory[0] != null)
 		{
diff --git a/src/main/java/mekanism/common/tile/TileEntityDynamicValve.java b/src/main/java/mekanism/common/tile/TileEntityDynamicValve.java
index 7405c3a72..2554e8d57 100644
--- a/src/main/java/mekanism/common/tile/TileEntityDynamicValve.java
+++ b/src/main/java/mekanism/common/tile/TileEntityDynamicValve.java
@@ -34,9 +34,9 @@ public class TileEntityDynamicValve extends TileEntityDynamicTank implements IFl
 	@Override
 	public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain)
 	{
-		if(fluidTank.dynamicTank.structure != null && fluidTank.dynamicTank.structure.fluidStored != null)
+		if(structure != null && structure.fluidStored != null)
 		{
-			if(resource.getFluid() == fluidTank.dynamicTank.structure.fluidStored.getFluid())
+			if(resource.getFluid() == structure.fluidStored.getFluid())
 			{
 				return fluidTank.drain(resource.amount, doDrain);
 			}
@@ -48,7 +48,7 @@ public class TileEntityDynamicValve extends TileEntityDynamicTank implements IFl
 	@Override
 	public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain)
 	{
-		if(fluidTank.dynamicTank.structure != null)
+		if(structure != null)
 		{
 			return fluidTank.drain(maxDrain, doDrain);
 		}
diff --git a/src/main/java/mekanism/common/tile/TileEntityMultiblock.java b/src/main/java/mekanism/common/tile/TileEntityMultiblock.java
index 6d4ffecb8..12ba3e5a2 100644
--- a/src/main/java/mekanism/common/tile/TileEntityMultiblock.java
+++ b/src/main/java/mekanism/common/tile/TileEntityMultiblock.java
@@ -20,7 +20,7 @@ import cpw.mods.fml.relauncher.SideOnly;
 
 import io.netty.buffer.ByteBuf;
 
-public abstract class TileEntityMultiblock<T> extends TileEntityContainerBlock implements IMultiblock<T>
+public abstract class TileEntityMultiblock<T extends SynchronizedData<T>> extends TileEntityContainerBlock implements IMultiblock<T>
 {
 	/** The tank data for this structure. */
 	public T structure;
@@ -138,7 +138,7 @@ public abstract class TileEntityMultiblock<T> extends TileEntityContainerBlock i
 		{
 			for(Coord4D obj : getSynchronizedData().locations)
 			{
-				TileEntityDynamicTank tileEntity = (TileEntityDynamicTank)obj.getTileEntity(worldObj);
+				TileEntityMultiblock<T> tileEntity = (TileEntityMultiblock<T>)obj.getTileEntity(worldObj);
 
 				if(tileEntity != null && tileEntity.isRendering)
 				{
@@ -244,8 +244,8 @@ public abstract class TileEntityMultiblock<T> extends TileEntityContainerBlock i
 	}
 
 	@Override
-	public SynchronizedData<T> getSynchronizedData() 
+	public T getSynchronizedData()
 	{
-		return (SynchronizedData<T>)structure;
+		return structure;
 	}
 }
diff --git a/src/main/java/mekanism/common/util/HeatUtils.java b/src/main/java/mekanism/common/util/HeatUtils.java
index 4dbd279f6..6c5013e80 100644
--- a/src/main/java/mekanism/common/util/HeatUtils.java
+++ b/src/main/java/mekanism/common/util/HeatUtils.java
@@ -26,7 +26,8 @@ public class HeatUtils
 				continue;
 			}
 			//Transfer to air otherwise
-			double heatToTransfer = source.getTemp() / (IHeatTransfer.AIR_INVERSE_COEFFICIENT+source.getInsulationCoefficient(side)+source.getInverseConductionCoefficient());
+			double invConduction = IHeatTransfer.AIR_INVERSE_COEFFICIENT + source.getInsulationCoefficient(side) + source.getInverseConductionCoefficient();
+			double heatToTransfer = source.getTemp() / invConduction;
 			source.transferHeatTo(-heatToTransfer);
 			heatTransferred[1] += heatToTransfer;
 		}
diff --git a/src/main/java/mekanism/generators/common/tile/TileEntityHeatGenerator.java b/src/main/java/mekanism/generators/common/tile/TileEntityHeatGenerator.java
index 14a7b7f6f..cb31091d1 100644
--- a/src/main/java/mekanism/generators/common/tile/TileEntityHeatGenerator.java
+++ b/src/main/java/mekanism/generators/common/tile/TileEntityHeatGenerator.java
@@ -432,7 +432,7 @@ public class TileEntityHeatGenerator extends TileEntityGenerator implements IFlu
 	@Override
 	public IHeatTransfer getAdjacent(ForgeDirection side)
 	{
-		if(side == ForgeDirection.DOWN)
+		if(canConnectHeat(side))
 		{
 			TileEntity adj = Coord4D.get(this).getFromSide(side).getTileEntity(worldObj);
 			if(adj instanceof IHeatTransfer)
diff --git a/src/main/resources/assets/mekanism/textures/blocks/ctm/BoilerValve-ctm.png b/src/main/resources/assets/mekanism/textures/blocks/ctm/BoilerValve-ctm.png
new file mode 100644
index 0000000000000000000000000000000000000000..51847a8995692fd044c7ed626d8ff3bb3c9a81c0
GIT binary patch
literal 1414
zcmV;11$p|3P)<h;3K|Lk000e1NJLTq002M$002M;1^@s6s%dfF000B@X+uL$Nkc;*
zP;zf(X>4Tx0C?Jsl+SMzWf;dlyYtSh2{;E+0gJ(jKY)-zwqTG3L3g{$wouDv+hW_8
znC<M_-Lccz@txW7BRz4^8>oMPiX1$ccrw9X5iY<%0|`MxY)DKvl=ve=5-x_+!%TMv
zXvO#R%=3Pq@AvyW?~{Sd=T+Z#(-zEnp{kVf*7(GvmAOHhXIMiwYpQnO=gQ?FfU57i
zJaX>+ObX!I_LcAd^Q@`Kz=qTlK(iLuvyeIlx@-F?1mhO4=V<7Mka-s9QsWbokjVmF
z^(Za?T~kp!4s@x}N<U;=z_c6H8f4~yo~cB>o`@q40Cbh4CzWGcl~Ud+tLC)h%7op=
zT>sBA>$aBlHUVpcnUO=lR!ALdsDVlxe_~gQBfyK0y6lJf3a}Ycf3#-Ca=<o7KQ*lm
zj79kLt0x+xZvyKeU30?0(Kvs?n;JeG)uzwPG!Io`%w;?1p9G$V^v#AG9E$vC)MTLu
z^nkXh(He;PwS&RjNO76xM5BK=;?dMQ)q`cA8?^UTvvN4{teuf=sS@#P7yYmtd(aj=
zcX%k`6s8P{Bg^@)F**>{irt|ajcdi^v~yrE#?Sk1c_{KG&Z<^rEXG`|s$wbP5r0c>
zEaucV)T)I;zz)#&b0k$o(qsyWjmLc~DwN1$;iG6Wje|=GiHAhtq@H93XBAu-iHhS6
zqK?F45ru=j3MNvx9b=key~v7PA}jWaA+b;F5q((VHSxOGFA5?n`o!LLZ8@QDQRjA>
z=UrMzEGir!f4aN1wWD`Zz3tdnPThH+y7`D_x$Gx70Rm>)$nDAgFU^0qp~G=qoO|oq
z+Dr2se%yTZTl%7YUB9K@&=)!g{a1ZKU(j!m!^NeJWEP2oha^ZiNcM06myjwhzv0nL
z;2uz%_D*+qZtN?k?#TCs?+tUZr<2Y8lKnG#J^N|)Z1(quro7^mw2!o}wC}aAwQsZ^
zv9$Br1?>mzqV}csZQ`v~-;eg&lS_0f@m8F*#5MEgM)L);U_NKQWDa%s%=KokIbd!v
z3+Bf5yCwCKy*NzZwx_zXR)ibF!{I0rML-o7kK+&Re_Sm#iC!`I(AMq|dqvx&Q8Wrh
z4$IhP^clUzK_l1kXKXPFMxU|8C_b=t_9`971S;9*cBYU7%%E_XL*f!3LwPI&=x_SR
zl~Zqo*3NA96)WfauCxX{dq<B|bzMt2^+p(2N(NHR$=VLaCnl}v7k#%v3P`QH(#eHy
zz`nmgyV}W3w&05Z*1z1zZS97qKZbL=?UtI0|M{uZPY_J++8KSV&4Ye(Y3c4}$b1O*
zKUiA&=giX5{m(#Kfb;IZSpac()l@2R00006VoOIv0RI600RN!9r;`8x010qNS#tmY
z3ljhU3ljkVnw%H_000McNliru-vI~(3>JyPJ+lA+0TW3?K~#9!?b{&^gfI|5(b)}#
z;7}+GqK5l$3NFA&xLQMc0R%|ou3ABMSI`Wtm_LpZGUZWPnhcL|9AA<#LI~1zU6$O=
zIeCil00000000000002MY7{Zw?p0O&J>N9V?Ry8!{Feca<B+y(rLOC*bIY>a{=~HN
z7crgK_g&_BKDABLlx)~)=4TQFfK?zahG94zZ`+o%<68Nn0M>Q=*;bZilAG7ee;J_f
z`>aD&J3o^k000000000000011cZ1G3+W_8svp4U(=M?||0000000000u=^=|^#bYx
zs1Kk%fJqPl%d5aAs1NwjMtwknpgw>}5C8xG00000002N~GZaF|-ichl&x?q>0lndJ
Uu*PGi>Hq)$07*qoM6N<$g5$}k*#H0l

literal 0
HcmV?d00001

diff --git a/src/main/resources/assets/mekanism/textures/blocks/ctm/BoilerValve.png b/src/main/resources/assets/mekanism/textures/blocks/ctm/BoilerValve.png
new file mode 100644
index 0000000000000000000000000000000000000000..f60ee83dd24df729a5db3fd55349336496c342d9
GIT binary patch
literal 1288
zcmV+j1^4=iP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F8000B@X+uL$Nkc;*
zP;zf(X>4Tx0C?Jsl+SMzWf;dlyYtSh2{;E+0gJ(jKY)-zwqTG3L3g{$wouDv+hW_8
znC<M_-Lccz@txW7BRz4^8>oMPiX1$ccrw9X5iY<%0|`MxY)DKvl=ve=5-x_+!%TMv
zXvO#R%=3Pq@AvyW?~{Sd=T+Z#(-zEnp{kVf*7(GvmAOHhXIMiwYpQnO=gQ?FfU57i
zJaX>+ObX!I_LcAd^Q@`Kz=qTlK(iLuvyeIlx@-F?1mhO4=V<7Mka-s9QsWbokjVmF
z^(Za?T~kp!4s@x}N<U;=z_c6H8f4~yo~cB>o`@q40Cbh4CzWGcl~Ud+tLC)h%7op=
zT>sBA>$aBlHUVpcnUO=lR!ALdsDVlxe_~gQBfyK0y6lJf3a}Ycf3#-Ca=<o7KQ*lm
zj79kLt0x+xZvyKeU30?0(Kvs?n;JeG)uzwPG!Io`%w;?1p9G$V^v#AG9E$vC)MTLu
z^nkXh(He;PwS&RjNO76xM5BK=;?dMQ)q`cA8?^UTvvN4{teuf=sS@#P7yYmtd(aj=
zcX%k`6s8P{Bg^@)F**>{irt|ajcdi^v~yrE#?Sk1c_{KG&Z<^rEXG`|s$wbP5r0c>
zEaucV)T)I;zz)#&b0k$o(qsyWjmLc~DwN1$;iG6Wje|=GiHAhtq@H93XBAu-iHhS6
zqK?F45ru=j3MNvx9b=key~v7PA}jWaA+b;F5q((VHSxOGFA5?n`o!LLZ8@QDQRjA>
z=UrMzEGir!f4aN1wWD`Zz3tdnPThH+y7`D_x$Gx70Rm>)$nDAgFU^0qp~G=qoO|oq
z+Dr2se%yTZTl%7YUB9K@&=)!g{a1ZKU(j!m!^NeJWEP2oha^ZiNcM06myjwhzv0nL
z;2uz%_D*+qZtN?k?#TCs?+tUZr<2Y8lKnG#J^N|)Z1(quro7^mw2!o}wC}aAwQsZ^
zv9$Br1?>mzqV}csZQ`v~-;eg&lS_0f@m8F*#5MEgM)L);U_NKQWDa%s%=KokIbd!v
z3+Bf5yCwCKy*NzZwx_zXR)ibF!{I0rML-o7kK+&Re_Sm#iC!`I(AMq|dqvx&Q8Wrh
z4$IhP^clUzK_l1kXKXPFMxU|8C_b=t_9`971S;9*cBYU7%%E_XL*f!3LwPI&=x_SR
zl~Zqo*3NA96)WfauCxX{dq<B|bzMt2^+p(2N(NHR$=VLaCnl}v7k#%v3P`QH(#eHy
zz`nmgyV}W3w&05Z*1z1zZS97qKZbL=?UtI0|M{uZPY_J++8KSV&4Ye(Y3c4}$b1O*
zKUiA&=giX5{m(#Kfb;IZSpac()l@2R00006VoOIv0RI600RN!9r;`8x010qNS#tmY
z3ljhU3ljkVnw%H_000McNliru-vI~(3>)`zK@I=_0F_BZK~z}7?bjg=Lm><X;D5_A
z1cyRlYq$@m-~yb4yOCZ10TQ{XiI;6+$ymi&M;pSIri5_GIp2r~QgJY|*x^KkD}+M<
z3Q&Lo!~w^r8<-i^+P8J@{a(OY3n2tJ=bmb<wf!9cfH@~>t*2ZmMThWDZowl`N~iT0
y<9C4G`^%g$W<Mats6(g#1t>rP-+zoGf5>+`x*?%3r13NW0000<MNUMnLSTZ^%5|Cm

literal 0
HcmV?d00001

diff --git a/src/main/resources/assets/mekanism/textures/blocks/ctm/SteamBoiler-ctm.png b/src/main/resources/assets/mekanism/textures/blocks/ctm/SteamBoiler-ctm.png
new file mode 100644
index 0000000000000000000000000000000000000000..352d7b87be1f849e32d16e9eb38968905ac8bfad
GIT binary patch
literal 1277
zcmaKs`#aMM9LK-Juri@sr&FnD93`nKx3$q+l2)X$lV@gl$YOFiVw{z=IkG}Wl3YSo
zifk%7GP!3i6AqKxoMTSR9%h?y<nRylc|Y&Z=XqYw`~B;O=Ie7-8*Bmw06^OVjlyhR
z*DY&;Ha9aodK>_@Er%nKz8*;AUf&oZAv`h!01^b5IlaVPAx7;k9BI!hEx5)-m5e5u
zODf|`T@V9=i05?G`F0UTaie*8;`!0BnlB@ZPe+!_ouc`sFUYBMsAFEjv8L;~HeAFu
zEd(+W?G4XaF-Pgyqz^QE$7_VODwcWDlxGsfnxiB;jVJP(i2q{1`?Z*vvoy5Q@;vtg
zB0n?$c`||-H!1r3&%RWqgyR^qu<i|>6v%TW8`4*mo!s;Reb-up;abKiP<_N2Ol{hr
zS`;=*Wh5HRVy5-88J((FnWYVYy?DV95?jglbioI5S*C*~_?fiAO4P@~d*(VoZ0>W5
z+~8}um~q(48P@dYJ7evE8@+aOb&q5xR3a#kA0WB@XtRhHX`WCr?sA4XH-k|>PgR1e
zjWJ{vp)H1c0_|d71^L}=W%;FrNhwivZZDSfsfiCKe|lo#3>_@*<sH}Y!|_5+&puNt
zq%L=-L!tEl!Upign#-wmm3)(XN;akGD?0kI2vtgk3;6@52Pq*%rYwbDZ47+r)zb%2
zm3oU2hU?YPq&@?u_ykMguJRpp^Dbqz?WvuE^s!=-%!t;%S)`z&ZM|zziIz);dYJwp
z)@^}ROx$6iF4>D{Ju`4}fO!|mh4d*%2mIXn@nBkJd$Khwrz3c(H0)R=#Y>AuMNK^~
z#DhY22O87p(~y$;Ey22XDGlI5nD=JE`M8D-I(EO6oW8H-{Jd^*fY5%ie)tG@ztw8X
zc-#$#V&Vl++!GP@D%u(b-AAtHu-T>Ym<I9iWuLLcqI%`M&=2z#4l1eyL5H~W`;l()
zyW-|(Y(dhTK;R{XrhL6EyS7@Af^96D=rcT}xqdkralMboXl^aLk2j!Es0Yw+wEU*Z
z9}JjH;}qX}a*uC;+$TOhYTl}1p+k`_(I&mE$nO6BaNRD+X)RnHDOXU-{}kr1vRH3e
z680N5k@ZszODa$oYlutaW%3)sD*uTr`DnIRtkt4_0lURsI^!-?uMKhz{fLL$TH(nE
zZybW6Y`4#}$vFpQ4;$d#lXVsKZ~gC>HaAaM>bbe@Hq4sV5t9RdK0*7%cBOA<K{v^5
zwCuuhusft{n6PZz(1tMoYcVv^7VQ44rq$B9`j#AK)}aO)5yh1=)ObZV^N>Qly~sps
zwOQh7d+tTzouQsc`ED!G+G`fbDd}vM-z}_{gUsD1z!QY&?i4`UEDlx88YaIx14dVI
z`aACuxgQ%{IfBk}b(0yuP~+Fm8{+f|mvj<G{3t(TLs>a+ey6uYV4PglO-Uhz|GPlw
zxUH#h3Anl{;`R5h_qtw7l`b@10iqKIFTtWW-v9{bbruC|Y^kWBB72i)-9%p@0l;?s
zEvo?e1xA}tBi7@bo5qBSruvRORAYA9CZc<w5SJ1J;?Wvy?omwup_iy{=q*Y+oOjT;
zSzWO`$fsnP)T0#Q#>|o=R<(9^<3@WnTR)t<zR$c7z|THT$&69`VG_vWe_NQ#dM`_M
b-Y?WRzXQVC_06+!tK#A2gL-`?=(qm?B7Rrq

literal 0
HcmV?d00001

diff --git a/src/main/resources/assets/mekanism/textures/blocks/ctm/SteamBoiler.png b/src/main/resources/assets/mekanism/textures/blocks/ctm/SteamBoiler.png
new file mode 100644
index 0000000000000000000000000000000000000000..a341a84e96217d211c566d553836051dfd9433a1
GIT binary patch
literal 1202
zcmV;j1Wo&iP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F8000B@X+uL$Nkc;*
zP;zf(X>4Tx0C?Jsl+SMzWf;dlyYtSh2{;E+0gJ(jKY)-zwqTG3L3g{$wouDv+hW_8
znC<M_-Lccz@txW7BRz4^8>oMPiX1$ccrw9X5iY<%0|`MxY)DKvl=ve=5-x_+!%TMv
zXvO#R%=3Pq@AvyW?~{Sd=T+Z#(-zEnp{kVf*7(GvmAOHhXIMiwYpQnO=gQ?FfU57i
zJaX>+ObX!I_LcAd^Q@`Kz=qTlK(iLuvyeIlx@-F?1mhO4=V<7Mka-s9QsWbokjVmF
z^(Za?T~kp!4s@x}N<U;=z_c6H8f4~yo~cB>o`@q40Cbh4CzWGcl~Ud+tLC)h%7op=
zT>sBA>$aBlHUVpcnUO=lR!ALdsDVlxe_~gQBfyK0y6lJf3a}Ycf3#-Ca=<o7KQ*lm
zj79kLt0x+xZvyKeU30?0(Kvs?n;JeG)uzwPG!Io`%w;?1p9G$V^v#AG9E$vC)MTLu
z^nkXh(He;PwS&RjNO76xM5BK=;?dMQ)q`cA8?^UTvvN4{teuf=sS@#P7yYmtd(aj=
zcX%k`6s8P{Bg^@)F**>{irt|ajcdi^v~yrE#?Sk1c_{KG&Z<^rEXG`|s$wbP5r0c>
zEaucV)T)I;zz)#&b0k$o(qsyWjmLc~DwN1$;iG6Wje|=GiHAhtq@H93XBAu-iHhS6
zqK?F45ru=j3MNvx9b=key~v7PA}jWaA+b;F5q((VHSxOGFA5?n`o!LLZ8@QDQRjA>
z=UrMzEGir!f4aN1wWD`Zz3tdnPThH+y7`D_x$Gx70Rm>)$nDAgFU^0qp~G=qoO|oq
z+Dr2se%yTZTl%7YUB9K@&=)!g{a1ZKU(j!m!^NeJWEP2oha^ZiNcM06myjwhzv0nL
z;2uz%_D*+qZtN?k?#TCs?+tUZr<2Y8lKnG#J^N|)Z1(quro7^mw2!o}wC}aAwQsZ^
zv9$Br1?>mzqV}csZQ`v~-;eg&lS_0f@m8F*#5MEgM)L);U_NKQWDa%s%=KokIbd!v
z3+Bf5yCwCKy*NzZwx_zXR)ibF!{I0rML-o7kK+&Re_Sm#iC!`I(AMq|dqvx&Q8Wrh
z4$IhP^clUzK_l1kXKXPFMxU|8C_b=t_9`971S;9*cBYU7%%E_XL*f!3LwPI&=x_SR
zl~Zqo*3NA96)WfauCxX{dq<B|bzMt2^+p(2N(NHR$=VLaCnl}v7k#%v3P`QH(#eHy
zz`nmgyV}W3w&05Z*1z1zZS97qKZbL=?UtI0|M{uZPY_J++8KSV&4Ye(Y3c4}$b1O*
zKUiA&=giX5{m(#Kfb;IZSpac()l@2R00006VoOIv0RI600RN!9r;`8x010qNS#tmY
z3ljhU3ljkVnw%H_000McNliru-vI~(3>a>D5VQaQ06$4YK~z}7?bksO05A*!LE4EI
z)FC-qS{Z!q46t8Ll4o13W_N4jwslnw0000000000000000000ONoqdi570LVqUWc)
Qk^lez07*qoM6N<$g1;z6MgRZ+

literal 0
HcmV?d00001