diff --git a/alloy-furnace-gui-1.xcf b/alloy-furnace-gui-1.xcf
index 4e8bee1..d6135f5 100644
Binary files a/alloy-furnace-gui-1.xcf and b/alloy-furnace-gui-1.xcf differ
diff --git a/alloy-furnace-gui-2.xcf b/alloy-furnace-gui-2.xcf
index 23699df..6c21c06 100644
Binary files a/alloy-furnace-gui-2.xcf and b/alloy-furnace-gui-2.xcf differ
diff --git a/src/java/mmm/food/FMilkable.java b/src/java/mmm/food/FMilkable.java
index cb2b671..492e6fa 100644
--- a/src/java/mmm/food/FMilkable.java
+++ b/src/java/mmm/food/FMilkable.java
@@ -7,7 +7,7 @@ import io.netty.buffer.ByteBuf;
 import mmm.Mmm;
 import mmm.utils.I_UMessage;
 import mmm.utils.URegistry;
-import net.minecraft.client.Minecraft;
+import net.minecraft.client.entity.EntityPlayerSP;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.passive.EntityAnimal;
 import net.minecraft.entity.player.EntityPlayer;
@@ -142,9 +142,9 @@ public class FMilkable
 
 		@Override
 		@SideOnly( Side.CLIENT )
-		public void handleOnClient( )
+		public void handleOnClient( final EntityPlayerSP player )
 		{
-			final Entity entity = Minecraft.getMinecraft( ).theWorld.getEntityByID( this.entityId );
+			final Entity entity = player.getEntityWorld( ).getEntityByID( this.entityId );
 			if ( entity == null || !entity.hasCapability( FMilkable.CAPABILITY , null ) ) {
 				return;
 			}
@@ -191,7 +191,7 @@ public class FMilkable
 
 			// Are we targeting a potentially milk-producing animal?
 			final Entity target = event.getTarget( );
-			if ( !target.hasCapability( CAPABILITY , null ) ) {
+			if ( !target.hasCapability( FMilkable.CAPABILITY , null ) ) {
 				return;
 			}
 
@@ -220,8 +220,8 @@ public class FMilkable
 
 			// Make sure it's been long enough since the animal's last milking
 			final EntityPlayer player = event.getEntityPlayer( );
-			FMilkable milkable = animal.getCapability( CAPABILITY , null );
-			long curTime = player.worldObj.getTotalWorldTime( );
+			final FMilkable milkable = animal.getCapability( FMilkable.CAPABILITY , null );
+			final long curTime = player.worldObj.getTotalWorldTime( );
 			if ( curTime - milkable.lastMilking < milkType.getPeriod( ) ) {
 				return;
 			}
diff --git a/src/java/mmm/materials/MAlloyRecipe.java b/src/java/mmm/materials/MAlloyRecipe.java
index 72a0c97..3ca6498 100644
--- a/src/java/mmm/materials/MAlloyRecipe.java
+++ b/src/java/mmm/materials/MAlloyRecipe.java
@@ -1,10 +1,7 @@
 package mmm.materials;
 
 
-import net.minecraft.client.resources.I18n;
 import net.minecraft.item.ItemStack;
-import net.minecraftforge.fml.relauncher.Side;
-import net.minecraftforge.fml.relauncher.SideOnly;
 
 
 
@@ -13,20 +10,17 @@ public class MAlloyRecipe
 
 	public static final int MAX_ALLOY_INPUTS = 6;
 
-	public final String name;
 	public final int burnTime;
 	public final float xp;
 	public final ItemStack output;
 	public final ItemStack[] inputs;
 
 
-	MAlloyRecipe( final String name , final int burnTime , final float xp , final ItemStack output ,
-			final ItemStack[] inputs )
+	MAlloyRecipe( final int burnTime , final float xp , final ItemStack output , final ItemStack[] inputs )
 	{
 		if ( inputs.length < 1 || inputs.length > MAlloyRecipe.MAX_ALLOY_INPUTS ) {
 			throw new IllegalArgumentException( "invalid alloy recipe" );
 		}
-		this.name = name;
 		this.burnTime = burnTime;
 		this.xp = xp;
 		this.output = output;
@@ -34,10 +28,9 @@ public class MAlloyRecipe
 	}
 
 
-	@SideOnly( Side.CLIENT )
 	public String getLocalizedName( )
 	{
-		return I18n.format( this.name );
+		return this.output.getItem( ).getItemStackDisplayName( this.output );
 	}
 
 }
diff --git a/src/java/mmm/materials/MAlloyRecipesRegistry.java b/src/java/mmm/materials/MAlloyRecipesRegistry.java
index 5cc10fa..0a0b620 100644
--- a/src/java/mmm/materials/MAlloyRecipesRegistry.java
+++ b/src/java/mmm/materials/MAlloyRecipesRegistry.java
@@ -2,8 +2,11 @@ package mmm.materials;
 
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Set;
 
+import com.google.common.collect.HashMultimap;
+
+import mmm.utils.UItemId;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 import net.minecraftforge.fml.relauncher.Side;
@@ -14,30 +17,16 @@ import net.minecraftforge.fml.relauncher.SideOnly;
 public enum MAlloyRecipesRegistry {
 	INSTANCE;
 
-	private final HashMap< String , MAlloyRecipe > recipes = new HashMap<>( );
+	private final HashMultimap< UItemId , MAlloyRecipe > recipes = HashMultimap.create( );
 
 
 	public void addRecipe( final int burnTime , final float xp , final Item output , final Object... params )
 	{
-		this.addRecipe( output.getUnlocalizedName( ) , burnTime , xp , new ItemStack( output ) , params );
+		this.addRecipe( burnTime , xp , new ItemStack( output ) , params );
 	}
 
 
 	public void addRecipe( final int burnTime , final float xp , final ItemStack output , final Object... params )
-	{
-		this.addRecipe( output.getUnlocalizedName( ) , burnTime , xp , output , params );
-	}
-
-
-	public void addRecipe( final String name , final int burnTime , final float xp , final Item output ,
-			final Object... params )
-	{
-		this.addRecipe( name , burnTime , xp , new ItemStack( output ) , params );
-	}
-
-
-	public void addRecipe( final String name , final int burnTime , final float xp , final ItemStack output ,
-			final Object... params )
 	{
 		final int nParams = params.length;
 		final ItemStack[] inputs = new ItemStack[ nParams ];
@@ -51,24 +40,20 @@ public enum MAlloyRecipesRegistry {
 				throw new IllegalArgumentException( "invalid alloy recipe input type" );
 			}
 		}
-		this.addRecipe( name , burnTime , xp , output , inputs );
+		this.addRecipe( burnTime , xp , output , inputs );
 	}
 
 
-	public void addRecipe( final String name , final int burnTime , final float xp , final ItemStack output ,
-			final ItemStack[] inputs )
+	public void addRecipe( final int burnTime , final float xp , final ItemStack output , final ItemStack[] inputs )
 	{
-		if ( this.recipes.containsKey( name ) ) {
-			throw new IllegalArgumentException( "duplicate alloy recipe '" + name + "'" );
-		}
-		final MAlloyRecipe recipe = new MAlloyRecipe( name , burnTime , xp , output , inputs );
-		this.recipes.put( name , recipe );
+		final MAlloyRecipe recipe = new MAlloyRecipe( burnTime , xp , output , inputs );
+		this.recipes.put( UItemId.fromItemStack( output ) , recipe );
 	}
 
 
-	public MAlloyRecipe getRecipe( final String name )
+	public Set< MAlloyRecipe > getRecipe( final UItemId item )
 	{
-		return this.recipes.get( name );
+		return this.recipes.get( item );
 	}
 
 
diff --git a/src/java/mmm/tech/base/TBAlloyFurnaceContainer.java b/src/java/mmm/tech/base/TBAlloyFurnaceContainer.java
index 33a6f2b..0201563 100644
--- a/src/java/mmm/tech/base/TBAlloyFurnaceContainer.java
+++ b/src/java/mmm/tech/base/TBAlloyFurnaceContainer.java
@@ -1,55 +1,78 @@
 package mmm.tech.base;
 
 
+import java.util.ArrayList;
+
+import mmm.materials.MAlloyRecipe;
+import mmm.materials.MAlloyRecipesRegistry;
 import mmm.utils.UContainers;
+import mmm.utils.UInventoryDisplay;
+import mmm.utils.UInventoryGrid;
+import mmm.utils.slots.USDisplay;
+import mmm.utils.slots.USFuel;
+import mmm.utils.slots.USOutput;
+import mmm.utils.slots.USVisibilityController;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.entity.player.InventoryPlayer;
 import net.minecraft.inventory.Container;
 import net.minecraft.inventory.Slot;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.World;
-import net.minecraftforge.fml.relauncher.Side;
-import net.minecraftforge.fml.relauncher.SideOnly;
 
 
 
 public class TBAlloyFurnaceContainer
 		extends Container
 {
-	private final World world;
-	private final BlockPos position;
+	private final ArrayList< MAlloyRecipe > recipes = MAlloyRecipesRegistry.INSTANCE.getSortedRecipes( );
 
-	// public final IInventory input;
-	// public final IInventory fuel;
-	// public final IInventory output;
+	public final TBAlloyFurnaceTileEntity tileEntity;
+	public final World world;
+	public final BlockPos position;
+
+	public final USVisibilityController visibilityController;
+	public final UInventoryGrid input;
+	public final UInventoryGrid fuel;
+	public final UInventoryGrid output;
+	public final UInventoryDisplay recipe;
 
 
-	@SideOnly( Side.CLIENT )
-	public TBAlloyFurnaceContainer( final InventoryPlayer playerInv , final World world )
+	public TBAlloyFurnaceContainer( final InventoryPlayer playerInv , final TBAlloyFurnaceTileEntity tileEntity )
 	{
-		this( playerInv , world , BlockPos.ORIGIN );
-	}
-
-
-	public TBAlloyFurnaceContainer( final InventoryPlayer playerInv , final World world , final BlockPos blockPos )
-	{
-		this.world = world;
-		this.position = blockPos;
-
-		// this.input = input;
-		// this.fuel = fuel;
-
-		// UContainers.addGrid( //
-		// ( i , x , y ) -> this.addSlotToContainer( new Slot( input , i , x , y ) ) , //
-		// 3 , 5 , 0 , 11 , 7 );
-
-		// UContainers.addGrid( //
-		// ( i , x , y ) -> this.addSlotToContainer( new SlotFurnaceFuel( fuel , i , x , y ) ) , //
-		// 2 , 2 , 0 , 79 , 61 );
+		this.tileEntity = tileEntity;
+		this.world = tileEntity.getWorld( );
+		this.position = tileEntity.getPos( );
 
+		this.visibilityController = new USVisibilityController( this.inventorySlots );
+		this.visibilityController.startGroup( );
 		UContainers.addPlayerInventory( //
 				( i , x , y ) -> this.addSlotToContainer( new Slot( playerInv , i , x , y ) ) , //
-				7 , 111 );
+				8 , 112 );
+
+		this.visibilityController.startGroup( );
+		this.input = tileEntity.input;
+		UContainers.addGrid( //
+				( i , x , y ) -> this.addSlotToContainer( new Slot( this.input , i , x , y ) ) , //
+				this.input.width , this.input.height , 0 , 12 , 8 );
+		this.fuel = tileEntity.fuel;
+		UContainers.addGrid( //
+				( i , x , y ) -> this.addSlotToContainer( new USFuel( this.fuel , i , x , y ) ) , //
+				this.fuel.width , this.fuel.height , 0 , 80 , 62 );
+		this.output = tileEntity.output;
+		UContainers.addGrid( //
+				( i , x , y ) -> {
+					this.addSlotToContainer( new USOutput( playerInv.player , this.output , i , x , y ) );
+				} , this.output.width , this.output.height , 0 , 130 , 8 );
+
+		this.recipe = new UInventoryDisplay( "Recipe" , 7 );
+		this.visibilityController.startGroup( );
+		UContainers.addGrid( //
+				( i , x , y ) -> this.addSlotToContainer( new USDisplay( this.recipe , i , x , y ) ) , //
+				3 , 2 , 0 , 25 , 37 , 8 , 8 );
+		this.addSlotToContainer( new USDisplay( this.recipe , 6 , 131 , 49 ) );
+
+		this.visibilityController.finalizeGroups( );
+		this.visibilityController.hideGroup( 2 );
 	}
 
 
@@ -61,4 +84,22 @@ public class TBAlloyFurnaceContainer
 						this.position.getZ( ) + .5 ) <= 64.;
 	}
 
+
+	public void setCurrentRecipe( int index , boolean confirm )
+	{
+		if ( index < 0 || index >= this.recipes.size( ) ) {
+			// XXX log
+			return;
+		}
+
+		MAlloyRecipe recipe = this.recipes.get( index );
+		this.recipe.clear( );
+		for ( int i = 0 ; i < recipe.inputs.length ; i++ ) {
+			this.recipe.setInventorySlotContents( i , recipe.inputs[ i ] );
+		}
+		this.recipe.setInventorySlotContents( 6 , recipe.output );
+		
+		// XXX confirm
+	}
+
 }
diff --git a/src/java/mmm/tech/base/TBAlloyFurnaceGui.java b/src/java/mmm/tech/base/TBAlloyFurnaceGui.java
index f2c27ea..5c4e24f 100644
--- a/src/java/mmm/tech/base/TBAlloyFurnaceGui.java
+++ b/src/java/mmm/tech/base/TBAlloyFurnaceGui.java
@@ -1,12 +1,17 @@
 package mmm.tech.base;
 
 
+import java.io.IOException;
+
 import mmm.Mmm;
+import mmm.materials.MAlloyRecipesRegistry;
+import mmm.utils.URegistry;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiButton;
 import net.minecraft.client.gui.inventory.GuiContainer;
 import net.minecraft.client.renderer.GlStateManager;
 import net.minecraft.entity.player.InventoryPlayer;
 import net.minecraft.util.ResourceLocation;
-import net.minecraft.world.World;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
 
@@ -16,18 +21,60 @@ import net.minecraftforge.fml.relauncher.SideOnly;
 public class TBAlloyFurnaceGui
 		extends GuiContainer
 {
+	@SideOnly( Side.CLIENT )
 	private static enum Tab {
-		MAIN( 207 , 0 ) ,
-		CONFIG( 191 , 0 );
+		MAIN( 207 , 0 , 1 ) ,
+		CONFIG( 191 , 0 , 2 );
 
 		public final int iconX;
 		public final int iconY;
+		public final int slotGroup;
 
 
-		private Tab( final int iconX , final int iconY )
+		private Tab( final int iconX , final int iconY , final int slotGroup )
 		{
 			this.iconX = iconX;
 			this.iconY = iconY;
+			this.slotGroup = slotGroup;
+		}
+	}
+
+	@SideOnly( Side.CLIENT )
+	private static class ArrowButton
+			extends GuiButton
+	{
+		private final boolean forward;
+
+
+		public ArrowButton( final int buttonID , final int x , final int y , final boolean forward )
+		{
+			super( buttonID , x , y , 12 , 19 , "" );
+			this.forward = forward;
+		}
+
+
+		@Override
+		public void drawButton( final Minecraft mc , final int mouseX , final int mouseY )
+		{
+			if ( this.visible ) {
+				mc.getTextureManager( ).bindTexture( TBAlloyFurnaceGui.TEXTURES[ 1 ] );
+				GlStateManager.color( 1f , 1f , 1f , 1f );
+
+				int texX = 176;
+				if ( !this.enabled ) {
+					texX += this.width * 2;
+				} else if ( mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width
+						&& mouseY < this.yPosition + this.height ) {
+					texX += this.width;
+				}
+
+				int texY = 0;
+				if ( !this.forward ) {
+					texY += this.height;
+				}
+
+				this.drawTexturedModalRect( this.xPosition , this.yPosition , texX , texY , this.width , this.height );
+			}
 		}
 	}
 
@@ -41,21 +88,71 @@ public class TBAlloyFurnaceGui
 	private static final int TAB_WIDTH = 26;
 	private static final int TAB_HEIGHT = 26;
 	private static final int TAB_BORDER = 4;
-	private static final int TABS_X = 176;
-	private static final int TABS_Y = 45;
+	private static final int TABS_TEXTURE_X = 176;
+	private static final int TABS_TEXTURE_Y = 45;
 	private static final int TAB_ICON_X = 5;
 	private static final int TAB_ICON_Y = 5;
 	private static final int TAB_ICON_WIDTH = 16;
 	private static final int TAB_ICON_HEIGHT = 16;
 
-	private final TBAlloyFurnaceGui.Tab selectedTab = Tab.MAIN;
+	private final TBAlloyFurnaceContainer container;
+	private ArrowButton bPrevious;
+	private ArrowButton bNext;
+	private TBAlloyFurnaceGui.Tab selectedTab = Tab.MAIN;
+	private int currentRecipe = 0;
 
 
-	public TBAlloyFurnaceGui( final InventoryPlayer inventoryPlayer , final World world )
+	public TBAlloyFurnaceGui( final InventoryPlayer inventoryPlayer , final TBAlloyFurnaceTileEntity tileEntity )
 	{
-		super( new TBAlloyFurnaceContainer( inventoryPlayer , world ) );
+		super( new TBAlloyFurnaceContainer( inventoryPlayer , tileEntity ) );
 		this.xSize = 176;
 		this.ySize = 194;
+
+		this.container = (TBAlloyFurnaceContainer) this.inventorySlots;
+		for ( final TBAlloyFurnaceGui.Tab tab : TBAlloyFurnaceGui.Tab.values( ) ) {
+			if ( tab != this.selectedTab ) {
+				this.container.visibilityController.hideGroup( tab.slotGroup );
+			}
+		}
+
+		this.setRecipe( 0 );
+	}
+
+
+	@Override
+	public void initGui( )
+	{
+		super.initGui( );
+
+		final int x = ( this.width - this.xSize ) / 2;
+		final int y = ( this.height - this.ySize ) / 2;
+
+		this.bPrevious = new ArrowButton( 1 , 8 + x , 47 + y , false );
+		this.bNext = new ArrowButton( 2 , 156 + x , 47 + y , true );
+		this.bPrevious.visible = this.bNext.visible = false;
+
+		this.buttonList.add( this.bNext );
+		this.buttonList.add( this.bPrevious );
+		this.enableConfigButtons( );
+	}
+
+
+	private void enableConfigButtons( )
+	{
+		if ( this.bNext != null ) {
+			this.bNext.enabled = this.currentRecipe < MAlloyRecipesRegistry.INSTANCE.getSortedRecipes( ).size( ) - 1;
+			this.bPrevious.enabled = this.currentRecipe > 0;
+		}
+	}
+
+
+	private void setRecipe( final int index )
+	{
+		this.container.setCurrentRecipe( index , false );
+		URegistry.network.sendToServer( new TBAlloyFurnaceMessage(
+				( (TBAlloyFurnaceContainer) this.inventorySlots ).position , index , false ) );
+		this.currentRecipe = index;
+		this.enableConfigButtons( );
 	}
 
 
@@ -85,6 +182,42 @@ public class TBAlloyFurnaceGui
 	}
 
 
+	@Override
+	protected void mouseClicked( final int mouseX , final int mouseY , final int mouseButton )
+			throws IOException
+	{
+		super.mouseClicked( mouseX , mouseY , mouseButton );
+
+		for ( final TBAlloyFurnaceGui.Tab tab : TBAlloyFurnaceGui.Tab.values( ) ) {
+			if ( this.selectedTab == tab ) {
+				continue;
+			}
+
+			final int tabOffsetX = tab.ordinal( ) * TBAlloyFurnaceGui.TAB_WIDTH;
+			final int tabX = this.guiLeft + tabOffsetX + TBAlloyFurnaceGui.TAB_BORDER;
+			final int tabY = this.guiTop - TBAlloyFurnaceGui.TAB_HEIGHT + TBAlloyFurnaceGui.TAB_BORDER;
+
+			if ( mouseX >= tabX && mouseY >= tabY && mouseX <= tabX + TBAlloyFurnaceGui.TAB_WIDTH
+					&& mouseY <= tabY + TBAlloyFurnaceGui.TAB_HEIGHT ) {
+				this.selectTab( tab );
+				return;
+			}
+		}
+	}
+
+
+	@Override
+	protected void actionPerformed( final GuiButton button )
+			throws IOException
+	{
+		if ( button == this.bNext ) {
+			this.setRecipe( this.currentRecipe + 1 );
+		} else if ( button == this.bPrevious ) {
+			this.setRecipe( this.currentRecipe - 1 );
+		}
+	}
+
+
 	private void drawTab( final TBAlloyFurnaceGui.Tab tab )
 	{
 		final boolean selected = this.selectedTab == tab;
@@ -93,8 +226,8 @@ public class TBAlloyFurnaceGui
 		final int tabY = this.guiTop - TBAlloyFurnaceGui.TAB_HEIGHT + TBAlloyFurnaceGui.TAB_BORDER;
 
 		this.drawTexturedModalRect( tabX , tabY , //
-				TBAlloyFurnaceGui.TABS_X + tabOffsetX ,
-				TBAlloyFurnaceGui.TABS_Y + ( selected ? TBAlloyFurnaceGui.TAB_HEIGHT : 0 ) , //
+				TBAlloyFurnaceGui.TABS_TEXTURE_X ,
+				TBAlloyFurnaceGui.TABS_TEXTURE_Y + ( selected ? TBAlloyFurnaceGui.TAB_HEIGHT : 0 ) , //
 				TBAlloyFurnaceGui.TAB_WIDTH , TBAlloyFurnaceGui.TAB_HEIGHT );
 
 		this.zLevel = 100f;
@@ -105,4 +238,19 @@ public class TBAlloyFurnaceGui
 		this.zLevel = 0;
 	}
 
+
+	private void selectTab( final TBAlloyFurnaceGui.Tab tab )
+	{
+		this.container.visibilityController //
+				.hideGroup( this.selectedTab.slotGroup ) //
+				.showGroup( tab.slotGroup );
+		this.selectedTab = tab;
+
+		this.bNext.visible = tab == Tab.CONFIG;
+		this.bPrevious.visible = tab == Tab.CONFIG;
+		if ( tab == Tab.CONFIG ) {
+			this.bNext.enabled = this.currentRecipe < MAlloyRecipesRegistry.INSTANCE.getSortedRecipes( ).size( ) - 1;
+			this.bPrevious.enabled = this.currentRecipe > 0;
+		}
+	}
 }
diff --git a/src/java/mmm/tech/base/TBAlloyFurnaceGuiHandler.java b/src/java/mmm/tech/base/TBAlloyFurnaceGuiHandler.java
index 06c3c95..c6dea30 100644
--- a/src/java/mmm/tech/base/TBAlloyFurnaceGuiHandler.java
+++ b/src/java/mmm/tech/base/TBAlloyFurnaceGuiHandler.java
@@ -16,12 +16,13 @@ public class TBAlloyFurnaceGuiHandler
 {
 
 	@Override
-	public Object getServerGuiElement( int ID , EntityPlayer player , World world , int x , int y , int z )
+	public Object getServerGuiElement( final int ID , final EntityPlayer player , final World world , final int x ,
+			final int y , final int z )
 	{
-		BlockPos pos = new BlockPos( x , y , z );
-		TileEntity tileEntity = world.getTileEntity( pos );
+		final BlockPos pos = new BlockPos( x , y , z );
+		final TileEntity tileEntity = world.getTileEntity( pos );
 		if ( tileEntity instanceof TBAlloyFurnaceTileEntity ) {
-			return new TBAlloyFurnaceContainer( player.inventory , world , pos );
+			return new TBAlloyFurnaceContainer( player.inventory , (TBAlloyFurnaceTileEntity) tileEntity );
 		}
 		return null;
 	}
@@ -29,11 +30,12 @@ public class TBAlloyFurnaceGuiHandler
 
 	@Override
 	@SideOnly( Side.CLIENT )
-	public Object getClientGuiElement( int ID , EntityPlayer player , World world , int x , int y , int z )
+	public Object getClientGuiElement( final int ID , final EntityPlayer player , final World world , final int x ,
+			final int y , final int z )
 	{
-		TileEntity tileEntity = world.getTileEntity( new BlockPos( x , y , z ) );
+		final TileEntity tileEntity = world.getTileEntity( new BlockPos( x , y , z ) );
 		if ( tileEntity instanceof TBAlloyFurnaceTileEntity ) {
-			return new TBAlloyFurnaceGui( player.inventory , world );
+			return new TBAlloyFurnaceGui( player.inventory , (TBAlloyFurnaceTileEntity) tileEntity );
 		}
 		return null;
 	}
diff --git a/src/java/mmm/tech/base/TBAlloyFurnaceMessage.java b/src/java/mmm/tech/base/TBAlloyFurnaceMessage.java
new file mode 100644
index 0000000..bbbc361
--- /dev/null
+++ b/src/java/mmm/tech/base/TBAlloyFurnaceMessage.java
@@ -0,0 +1,78 @@
+package mmm.tech.base;
+
+
+import io.netty.buffer.ByteBuf;
+import mmm.utils.I_UMessage;
+import net.minecraft.client.entity.EntityPlayerSP;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.inventory.Container;
+import net.minecraft.util.math.BlockPos;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
+
+
+
+public class TBAlloyFurnaceMessage
+		implements I_UMessage
+{
+	private BlockPos blockPos;
+	private int selectedIndex;
+	private boolean confirm;
+
+
+	public TBAlloyFurnaceMessage( )
+	{
+		// EMPTY
+	}
+
+
+	public TBAlloyFurnaceMessage( final BlockPos blockPos , final int selectedIndex , final boolean confirm )
+	{
+		this.blockPos = blockPos;
+		this.selectedIndex = selectedIndex;
+		this.confirm = confirm;
+	}
+
+
+	@Override
+	public void fromBytes( final ByteBuf buf )
+	{
+		this.blockPos = new BlockPos( buf.readInt( ) , buf.readInt( ) , buf.readInt( ) );
+		this.selectedIndex = buf.readShort( );
+		this.confirm = buf.readBoolean( );
+	}
+
+
+	@Override
+	public void toBytes( final ByteBuf buf )
+	{
+		buf.writeInt( this.blockPos.getX( ) );
+		buf.writeInt( this.blockPos.getY( ) );
+		buf.writeInt( this.blockPos.getZ( ) );
+		buf.writeShort( this.selectedIndex );
+		buf.writeBoolean( this.confirm );
+	}
+
+
+	@Override
+	@SideOnly( Side.CLIENT )
+	public void handleOnClient( final EntityPlayerSP player )
+	{
+		// TODO Auto-generated method stub
+	}
+
+
+	@Override
+	public void handleOnServer( final EntityPlayerMP player )
+	{
+		final Container curCont = player.openContainer;
+		if ( ! ( curCont instanceof TBAlloyFurnaceContainer ) ) {
+			// XXX log?
+			return;
+		}
+
+		final TBAlloyFurnaceContainer container = (TBAlloyFurnaceContainer) curCont;
+		container.setCurrentRecipe( this.selectedIndex , this.confirm );
+	}
+
+}
diff --git a/src/java/mmm/tech/base/TBAlloyFurnaceTileEntity.java b/src/java/mmm/tech/base/TBAlloyFurnaceTileEntity.java
index c716a2a..b8d3b47 100644
--- a/src/java/mmm/tech/base/TBAlloyFurnaceTileEntity.java
+++ b/src/java/mmm/tech/base/TBAlloyFurnaceTileEntity.java
@@ -1,7 +1,10 @@
 package mmm.tech.base;
 
 
+import mmm.utils.UInventoryGrid;
+import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.tileentity.TileEntity;
+import net.minecraftforge.common.util.Constants.NBT;
 
 
 
@@ -9,4 +12,37 @@ public class TBAlloyFurnaceTileEntity
 		extends TileEntity
 {
 
+	public final UInventoryGrid input;
+	public final UInventoryGrid fuel;
+	public final UInventoryGrid output;
+
+
+	public TBAlloyFurnaceTileEntity( )
+	{
+		this.input = new UInventoryGrid( "Input" , 3 , 5 );
+		this.fuel = new UInventoryGrid( "Fuel" , 2 , 2 );
+		this.output = new UInventoryGrid( "Output" , 2 , 5 );
+	}
+
+
+	@Override
+	public void readFromNBT( NBTTagCompound compound )
+	{
+		super.readFromNBT( compound );
+		this.input.deserializeNBT( compound.getTagList( "Input" , NBT.TAG_COMPOUND ) );
+		this.fuel.deserializeNBT( compound.getTagList( "Fuel" , NBT.TAG_COMPOUND ) );
+		this.output.deserializeNBT( compound.getTagList( "Output" , NBT.TAG_COMPOUND ) );
+	}
+
+
+	@Override
+	public NBTTagCompound writeToNBT( NBTTagCompound compound )
+	{
+		super.writeToNBT( compound );
+		compound.setTag( "Input" , this.input.serializeNBT( ) );
+		compound.setTag( "Fuel" , this.fuel.serializeNBT( ) );
+		compound.setTag( "Output" , this.output.serializeNBT( ) );
+		return compound;
+	}
+
 }
diff --git a/src/java/mmm/tech/base/TechBase.java b/src/java/mmm/tech/base/TechBase.java
index 1a76e55..58d5e84 100644
--- a/src/java/mmm/tech/base/TechBase.java
+++ b/src/java/mmm/tech/base/TechBase.java
@@ -2,7 +2,9 @@ package mmm.tech.base;
 
 
 import mmm.Mmm;
+import mmm.materials.MAlloyRecipesRegistry;
 import mmm.utils.URegistry;
+import net.minecraft.init.Items;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemBlockSpecial;
 import net.minecraftforge.fml.common.network.NetworkRegistry;
@@ -27,6 +29,11 @@ public class TechBase
 		URegistry.addBlock( TechBase.ALLOY_FURNACE_BLOCK_ACTIVE , null );
 		GameRegistry.registerTileEntity( TBAlloyFurnaceTileEntity.class , "mmm:tech/base/alloy_furnace" );
 		NetworkRegistry.INSTANCE.registerGuiHandler( Mmm.get( ) , new TBAlloyFurnaceGuiHandler( ) );
+		URegistry.addServerMessage( TBAlloyFurnaceMessage.class );
+
+		// FIXME test, remove this later
+		MAlloyRecipesRegistry.INSTANCE.addRecipe( 200 , 0.05f , Items.COOKED_CHICKEN , Items.COOKED_BEEF ,
+				Items.COOKED_PORKCHOP );
 	}
 
 
diff --git a/src/java/mmm/utils/I_UMessage.java b/src/java/mmm/utils/I_UMessage.java
index 2c62562..8a51828 100644
--- a/src/java/mmm/utils/I_UMessage.java
+++ b/src/java/mmm/utils/I_UMessage.java
@@ -1,7 +1,11 @@
 package mmm.utils;
 
 
+import net.minecraft.client.entity.EntityPlayerSP;
+import net.minecraft.entity.player.EntityPlayerMP;
 import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
+import net.minecraftforge.fml.relauncher.Side;
+import net.minecraftforge.fml.relauncher.SideOnly;
 
 
 
@@ -9,13 +13,14 @@ public interface I_UMessage
 		extends IMessage
 {
 
-	default void handleOnClient( )
+	@SideOnly( Side.CLIENT )
+	default void handleOnClient( final EntityPlayerSP player )
 	{
 		// EMPTY
 	}
 
 
-	default void handleOnServer( )
+	default void handleOnServer( final EntityPlayerMP player )
 	{
 		// EMPTY
 	}
diff --git a/src/java/mmm/utils/UContainers.java b/src/java/mmm/utils/UContainers.java
index 1641574..1117af1 100644
--- a/src/java/mmm/utils/UContainers.java
+++ b/src/java/mmm/utils/UContainers.java
@@ -1,5 +1,6 @@
 package mmm.utils;
 
+
 public class UContainers
 {
 
@@ -26,9 +27,16 @@ public class UContainers
 	public static void addGrid( final UContainers.SlotAdder slotAdder , final int columns , final int rows ,
 			final int index , final int x , final int y )
 	{
-		for ( int row = 0 ; row < rows ; ++row ) {
-			for ( int column = 0 ; column < columns ; ++column ) {
-				slotAdder.addSlot( index + column + row * 9 , x + column * 18 , y + row * 18 );
+		addGrid( slotAdder , columns , rows , index , x , y , 2 , 2 );
+	}
+
+
+	public static void addGrid( final UContainers.SlotAdder slotAdder , final int columns , final int rows ,
+			final int index , final int x , final int y , int xSpacing , int ySpacing )
+	{
+		for ( int row = 0 , i = 0 ; row < rows ; ++row ) {
+			for ( int column = 0 ; column < columns ; ++column , ++i ) {
+				slotAdder.addSlot( index + i , x + column * ( 16 + xSpacing ) , y + row * ( 16 + ySpacing ) );
 			}
 		}
 	}
diff --git a/src/java/mmm/utils/UInventoryDisplay.java b/src/java/mmm/utils/UInventoryDisplay.java
new file mode 100644
index 0000000..6bbe8b7
--- /dev/null
+++ b/src/java/mmm/utils/UInventoryDisplay.java
@@ -0,0 +1,159 @@
+package mmm.utils;
+
+
+import java.util.Arrays;
+
+import javax.annotation.Nullable;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.text.ITextComponent;
+import net.minecraft.util.text.TextComponentTranslation;
+
+
+
+public class UInventoryDisplay
+		implements IInventory
+{
+	public final String name;
+	public final int size;
+	private ItemStack[] contents;
+
+
+	public UInventoryDisplay( final String name , final int size )
+	{
+		this.name = name;
+		this.size = size;
+		this.contents = new ItemStack[ size ];
+	}
+
+
+	@Override
+	public String getName( )
+	{
+		return this.name;
+	}
+
+
+	@Override
+	public boolean hasCustomName( )
+	{
+		return false;
+	}
+
+
+	@Override
+	public ITextComponent getDisplayName( )
+	{
+		return new TextComponentTranslation( this.name );
+	}
+
+
+	@Override
+	public int getSizeInventory( )
+	{
+		return this.size;
+	}
+
+
+	@Override
+	@Nullable
+	public ItemStack getStackInSlot( final int index )
+	{
+		return index >= 0 && index < this.size ? this.contents[ index ] : null;
+	}
+
+
+	@Override
+	public ItemStack decrStackSize( int index , int count )
+	{
+		return null;
+	}
+
+
+	@Override
+	public ItemStack removeStackFromSlot( int index )
+	{
+		return null;
+	}
+
+
+	@Override
+	public void setInventorySlotContents( int index , ItemStack stack )
+	{
+		this.contents[ index ] = stack;
+		this.markDirty( );
+	}
+
+
+	@Override
+	public int getInventoryStackLimit( )
+	{
+		return 64;
+	}
+
+
+	@Override
+	public boolean isUseableByPlayer( final EntityPlayer player )
+	{
+		return false;
+	}
+
+
+	@Override
+	public void openInventory( final EntityPlayer player )
+	{
+		// EMPTY
+	}
+
+
+	@Override
+	public void closeInventory( final EntityPlayer player )
+	{
+		// EMPTY
+	}
+
+
+	@Override
+	public boolean isItemValidForSlot( final int index , final ItemStack stack )
+	{
+		return true;
+	}
+
+
+	@Override
+	public int getField( final int id )
+	{
+		return 0;
+	}
+
+
+	@Override
+	public void setField( final int id , final int value )
+	{
+		// EMPTY
+	}
+
+
+	@Override
+	public int getFieldCount( )
+	{
+		return 0;
+	}
+
+
+	@Override
+	public void clear( )
+	{
+		Arrays.fill( this.contents , null );
+	}
+
+
+	@Override
+	public void markDirty( )
+	{
+		// EMPTY
+	}
+
+}
diff --git a/src/java/mmm/utils/A_UInventoryGrid.java b/src/java/mmm/utils/UInventoryGrid.java
similarity index 72%
rename from src/java/mmm/utils/A_UInventoryGrid.java
rename to src/java/mmm/utils/UInventoryGrid.java
index 68f4db0..a6eabe8 100644
--- a/src/java/mmm/utils/A_UInventoryGrid.java
+++ b/src/java/mmm/utils/UInventoryGrid.java
@@ -9,13 +9,16 @@ import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.inventory.IInventory;
 import net.minecraft.inventory.ItemStackHelper;
 import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.nbt.NBTTagList;
 import net.minecraft.util.text.ITextComponent;
 import net.minecraft.util.text.TextComponentTranslation;
+import net.minecraftforge.common.util.INBTSerializable;
 
 
 
-public abstract class A_UInventoryGrid
-		implements IInventory
+public class UInventoryGrid
+		implements IInventory , INBTSerializable< NBTTagList >
 {
 	public final String name;
 	public final int width;
@@ -24,7 +27,7 @@ public abstract class A_UInventoryGrid
 	private final ItemStack[] inventoryContents;
 
 
-	public A_UInventoryGrid( final String name , final int width , final int height )
+	public UInventoryGrid( final String name , final int width , final int height )
 	{
 		this.name = name;
 		this.width = width;
@@ -175,4 +178,43 @@ public abstract class A_UInventoryGrid
 		Arrays.fill( this.inventoryContents , null );
 	}
 
+
+	@Override
+	public void markDirty( )
+	{
+		// EMPTY
+	}
+
+
+	@Override
+	public NBTTagList serializeNBT( )
+	{
+		final NBTTagList list = new NBTTagList( );
+		for ( int i = 0 ; i < this.slotsCount ; i++ ) {
+			final ItemStack stack = this.inventoryContents[ i ];
+			NBTTagCompound tag;
+			if ( stack == null ) {
+				tag = new NBTTagCompound( );
+			} else {
+				tag = stack.serializeNBT( );
+			}
+			list.appendTag( tag );
+		}
+		return list;
+	}
+
+
+	@Override
+	public void deserializeNBT( final NBTTagList nbt )
+	{
+		final int n = Math.min( nbt.tagCount( ) , this.slotsCount );
+		Arrays.fill( this.inventoryContents , null );
+		for ( int i = 0 ; i < n ; i++ ) {
+			final NBTTagCompound tag = nbt.getCompoundTagAt( i );
+			if ( tag.hasKey( "id" ) ) {
+				this.inventoryContents[ i ] = ItemStack.loadItemStackFromNBT( tag );
+			}
+		}
+	}
+
 }
diff --git a/src/java/mmm/utils/UItemId.java b/src/java/mmm/utils/UItemId.java
new file mode 100644
index 0000000..c806681
--- /dev/null
+++ b/src/java/mmm/utils/UItemId.java
@@ -0,0 +1,93 @@
+package mmm.utils;
+
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.util.ResourceLocation;
+
+
+
+public class UItemId
+{
+	private static final ThreadLocal< WeakHashMap< UItemId , WeakReference< UItemId > > > TABLE //
+			= new ThreadLocal< WeakHashMap< UItemId , WeakReference< UItemId > > >( ) {
+				@Override
+				protected WeakHashMap< UItemId , WeakReference< UItemId > > initialValue( )
+				{
+					return new WeakHashMap<>( );
+				}
+			};
+
+
+	public static UItemId fromItemStack( final ItemStack stack )
+	{
+		return UItemId.create( stack.getItem( ) , stack.getItemDamage( ) );
+	}
+
+
+	public static UItemId create( final Item item )
+	{
+		return UItemId.create( item , 0 );
+	}
+
+
+	public static UItemId create( final Item item , final int meta )
+	{
+		final UItemId id = new UItemId( item.getRegistryName( ) , meta );
+		final WeakHashMap< UItemId , WeakReference< UItemId > > table = UItemId.TABLE.get( );
+		if ( table.containsKey( id ) ) {
+			return table.get( id ).get( );
+		}
+		table.put( id , new WeakReference< UItemId >( id ) );
+		return id;
+	}
+
+	public final ResourceLocation name;
+	public final int meta;
+
+
+	private UItemId( final ResourceLocation name , final int meta )
+	{
+		this.name = name;
+		this.meta = meta;
+	}
+
+
+	@Override
+	public int hashCode( )
+	{
+		final int prime = 31;
+		return prime * ( prime + this.meta ) + ( this.name == null ? 0 : this.name.hashCode( ) );
+	}
+
+
+	@Override
+	public boolean equals( final Object obj )
+	{
+		if ( this == obj ) {
+			return true;
+		}
+		if ( obj == null ) {
+			return false;
+		}
+		if ( this.getClass( ) != obj.getClass( ) ) {
+			return false;
+		}
+		final UItemId other = (UItemId) obj;
+		if ( this.meta != other.meta ) {
+			return false;
+		}
+		if ( this.name == null ) {
+			if ( other.name != null ) {
+				return false;
+			}
+		} else if ( !this.name.equals( other.name ) ) {
+			return false;
+		}
+		return true;
+	}
+
+}
diff --git a/src/java/mmm/utils/URegistry.java b/src/java/mmm/utils/URegistry.java
index 8626ac6..c2cc5e1 100644
--- a/src/java/mmm/utils/URegistry.java
+++ b/src/java/mmm/utils/URegistry.java
@@ -209,7 +209,7 @@ public class URegistry
 		URegistry.network.registerMessage( //
 				( final I_UMessage m , final MessageContext ctx ) -> {
 					final IThreadListener main = Minecraft.getMinecraft( );
-					main.addScheduledTask( ( ) -> m.handleOnClient( ) );
+					main.addScheduledTask( ( ) -> m.handleOnClient( Minecraft.getMinecraft( ).thePlayer ) );
 					return null;
 				} , //
 				message , URegistry.nextPacketDiscriminator++ , Side.CLIENT );
@@ -221,7 +221,7 @@ public class URegistry
 		URegistry.network.registerMessage( //
 				( final I_UMessage m , final MessageContext ctx ) -> {
 					final IThreadListener main = (WorldServer) ctx.getServerHandler( ).playerEntity.worldObj;
-					main.addScheduledTask( ( ) -> m.handleOnServer( ) );
+					main.addScheduledTask( ( ) -> m.handleOnServer( ctx.getServerHandler( ).playerEntity ) );
 					return null;
 				} , //
 				message , URegistry.nextPacketDiscriminator++ , Side.SERVER );
diff --git a/src/java/mmm/utils/slots/USDisplay.java b/src/java/mmm/utils/slots/USDisplay.java
new file mode 100644
index 0000000..9375080
--- /dev/null
+++ b/src/java/mmm/utils/slots/USDisplay.java
@@ -0,0 +1,35 @@
+package mmm.utils.slots;
+
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+
+
+
+/** An inventory slot that cannot be interacted with */
+public class USDisplay
+		extends Slot
+{
+
+	public USDisplay( IInventory inventoryIn , int index , int xPosition , int yPosition )
+	{
+		super( inventoryIn , index , xPosition , yPosition );
+	}
+
+
+	@Override
+	public boolean isItemValid( ItemStack stack )
+	{
+		return false;
+	}
+
+
+	@Override
+	public boolean canTakeStack( EntityPlayer playerIn )
+	{
+		return false;
+	}
+
+}
diff --git a/src/java/mmm/utils/slots/USFuel.java b/src/java/mmm/utils/slots/USFuel.java
new file mode 100644
index 0000000..de1981f
--- /dev/null
+++ b/src/java/mmm/utils/slots/USFuel.java
@@ -0,0 +1,29 @@
+package mmm.utils.slots;
+
+
+import javax.annotation.Nullable;
+
+import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+import net.minecraft.tileentity.TileEntityFurnace;
+
+
+
+public class USFuel
+		extends Slot
+{
+
+	public USFuel( final IInventory inventoryIn , final int slotIndex , final int xPosition , final int yPosition )
+	{
+		super( inventoryIn , slotIndex , xPosition , yPosition );
+	}
+
+
+	@Override
+	public boolean isItemValid( @Nullable final ItemStack stack )
+	{
+		return TileEntityFurnace.isItemFuel( stack );
+	}
+
+}
diff --git a/src/java/mmm/utils/slots/USOutput.java b/src/java/mmm/utils/slots/USOutput.java
new file mode 100644
index 0000000..16359b3
--- /dev/null
+++ b/src/java/mmm/utils/slots/USOutput.java
@@ -0,0 +1,91 @@
+package mmm.utils.slots;
+
+
+import javax.annotation.Nullable;
+
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.Slot;
+import net.minecraft.item.ItemStack;
+
+
+
+public class USOutput
+		extends Slot
+{
+	public static interface I_OutputHandler
+	{
+		public void onOutputRemoved( EntityPlayer player , ItemStack stack , int quantity );
+	}
+
+	/** Player interacting with this slot */
+	private final EntityPlayer player;
+	/** Output handler */
+	@Nullable
+	private final I_OutputHandler outputHandler;
+	/** Amount of items removed from the slot */
+	private int removed;
+
+
+	public USOutput( final EntityPlayer player , final IInventory inventoryIn , final int slotIndex ,
+			final int xPosition , final int yPosition )
+	{
+		this( player , inventoryIn , slotIndex , xPosition , yPosition , null );
+	}
+
+
+	public USOutput( final EntityPlayer player , final IInventory inventoryIn , final int slotIndex ,
+			final int xPosition , final int yPosition , @Nullable final I_OutputHandler handler )
+	{
+		super( inventoryIn , slotIndex , xPosition , yPosition );
+		this.player = player;
+		this.outputHandler = handler;
+		this.removed = 0;
+	}
+
+
+	/** Players can't add items to output slots */
+	@Override
+	public boolean isItemValid( @Nullable final ItemStack stack )
+	{
+		return false;
+	}
+
+
+	@Override
+	public ItemStack decrStackSize( final int amount )
+	{
+		if ( this.getHasStack( ) ) {
+			this.removed += Math.min( amount , this.getStack( ).stackSize );
+		}
+		return super.decrStackSize( amount );
+	}
+
+
+	@Override
+	public void onPickupFromSlot( final EntityPlayer playerIn , final ItemStack stack )
+	{
+		this.onCrafting( stack );
+		super.onPickupFromSlot( playerIn , stack );
+	}
+
+
+	@Override
+	protected void onCrafting( final ItemStack stack , final int amount )
+	{
+		this.removed += amount;
+		this.onCrafting( stack );
+	}
+
+
+	@Override
+	protected void onCrafting( final ItemStack stack )
+	{
+		stack.onCrafting( this.player.worldObj , this.player , this.removed );
+		if ( this.outputHandler != null ) {
+			this.outputHandler.onOutputRemoved( this.player , stack , this.removed );
+		}
+		this.removed = 0;
+	}
+
+}
diff --git a/src/java/mmm/utils/slots/USVisibilityController.java b/src/java/mmm/utils/slots/USVisibilityController.java
new file mode 100644
index 0000000..1ff51d7
--- /dev/null
+++ b/src/java/mmm/utils/slots/USVisibilityController.java
@@ -0,0 +1,83 @@
+package mmm.utils.slots;
+
+
+import java.util.List;
+
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import net.minecraft.inventory.Slot;
+
+
+
+public class USVisibilityController
+{
+	private final List< Slot > slots;
+	private final IntArrayList firstSlots;
+	private int[] slotX;
+	private int[] slotY;
+
+
+	public USVisibilityController( final List< Slot > slots )
+	{
+		this.slots = slots;
+		this.firstSlots = new IntArrayList( );
+	}
+
+
+	public int startGroup( )
+	{
+		this.firstSlots.add( this.slots.size( ) );
+		return this.firstSlots.size( ) - 1;
+	}
+
+
+	public void finalizeGroups( )
+	{
+		final int nSlots = this.slots.size( );
+		this.slotX = new int[ nSlots ];
+		this.slotY = new int[ nSlots ];
+		for ( int i = 0 ; i < nSlots ; i++ ) {
+			final Slot slot = this.slots.get( i );
+			this.slotX[ i ] = slot.xDisplayPosition;
+			this.slotY[ i ] = slot.yDisplayPosition;
+		}
+	}
+
+
+	public USVisibilityController showGroup( final int index )
+	{
+		final int first = this.firstSlots.getInt( index );
+		final int last;
+		if ( index == this.firstSlots.size( ) - 1 ) {
+			last = this.slotX.length;
+		} else {
+			last = this.firstSlots.getInt( index + 1 );
+		}
+
+		for ( int i = first ; i < last ; i++ ) {
+			final Slot slot = this.slots.get( i );
+			slot.xDisplayPosition = this.slotX[ i ];
+			slot.yDisplayPosition = this.slotY[ i ];
+		}
+
+		return this;
+	}
+
+
+	public USVisibilityController hideGroup( final int index )
+	{
+		final int first = this.firstSlots.getInt( index );
+		final int last;
+		if ( index == this.firstSlots.size( ) - 1 ) {
+			last = this.slotX.length;
+		} else {
+			last = this.firstSlots.getInt( index + 1 );
+		}
+
+		for ( int i = first ; i < last ; i++ ) {
+			final Slot slot = this.slots.get( i );
+			slot.xDisplayPosition = slot.yDisplayPosition = -4000;
+		}
+
+		return this;
+	}
+}
diff --git a/src/resources/assets/mmm/textures/gui/alloy-furnace_2.png b/src/resources/assets/mmm/textures/gui/alloy-furnace_2.png
deleted file mode 100644
index a402554..0000000
Binary files a/src/resources/assets/mmm/textures/gui/alloy-furnace_2.png and /dev/null differ
diff --git a/src/resources/assets/mmm/textures/gui/alloy_furnace_1.png b/src/resources/assets/mmm/textures/gui/alloy_furnace_1.png
index 2f949de..36cbaad 100644
Binary files a/src/resources/assets/mmm/textures/gui/alloy_furnace_1.png and b/src/resources/assets/mmm/textures/gui/alloy_furnace_1.png differ
diff --git a/src/resources/assets/mmm/textures/gui/alloy_furnace_2.png b/src/resources/assets/mmm/textures/gui/alloy_furnace_2.png
new file mode 100644
index 0000000..52eba1a
Binary files /dev/null and b/src/resources/assets/mmm/textures/gui/alloy_furnace_2.png differ