From b5176a9f49925a1ea5ac7391384c057293d4ddee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@nocternity.net>
Date: Thu, 23 Jun 2016 16:40:53 +0200
Subject: [PATCH] Alloy furnace - Alloying!

---
 src/java/mmm/materials/MAlloyRecipe.java      |  30 ++
 .../mmm/tech/base/TBAlloyFurnaceBlock.java    |  56 +++-
 src/java/mmm/tech/base/TBAlloyFurnaceGui.java |  22 ++
 .../tech/base/TBAlloyFurnaceTileEntity.java   | 277 +++++++++++++++++-
 src/java/mmm/tech/base/TechBase.java          |   2 +-
 src/java/mmm/utils/UInventoryGrid.java        |   2 +-
 6 files changed, 382 insertions(+), 7 deletions(-)

diff --git a/src/java/mmm/materials/MAlloyRecipe.java b/src/java/mmm/materials/MAlloyRecipe.java
index 7ee07e3..4069428 100644
--- a/src/java/mmm/materials/MAlloyRecipe.java
+++ b/src/java/mmm/materials/MAlloyRecipe.java
@@ -7,6 +7,7 @@ import java.util.HashMap;
 import mmm.Mmm;
 import mmm.utils.UItemId;
 import net.minecraft.block.Block;
+import net.minecraft.inventory.IInventory;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.ResourceLocation;
@@ -234,4 +235,33 @@ public class MAlloyRecipe
 		return this.output.getItem( ).getItemStackDisplayName( this.output );
 	}
 
+
+	public boolean checkInventory( final IInventory input )
+	{
+		return this.checkInventory( input , 0 , input.getSizeInventory( ) );
+	}
+
+
+	public boolean checkInventory( final IInventory input , final int first , final int last )
+	{
+		for ( int i = 0 ; i < this.inputs.length ; i++ ) {
+			final ItemStack inputStack = this.inputs[ i ];
+			int found = 0;
+			for ( int slot = first ; slot < last ; slot++ ) {
+				final ItemStack stackInSlot = input.getStackInSlot( slot );
+				if ( stackInSlot == null || !inputStack.isItemEqual( stackInSlot ) ) {
+					continue;
+				}
+				found += stackInSlot.stackSize;
+				if ( found >= inputStack.stackSize ) {
+					break;
+				}
+			}
+			if ( found < inputStack.stackSize ) {
+				return false;
+			}
+		}
+		return true;
+	}
+
 }
diff --git a/src/java/mmm/tech/base/TBAlloyFurnaceBlock.java b/src/java/mmm/tech/base/TBAlloyFurnaceBlock.java
index 82505ff..44504b7 100644
--- a/src/java/mmm/tech/base/TBAlloyFurnaceBlock.java
+++ b/src/java/mmm/tech/base/TBAlloyFurnaceBlock.java
@@ -22,6 +22,7 @@ import net.minecraft.entity.Entity;
 import net.minecraft.entity.EntityLivingBase;
 import net.minecraft.entity.player.EntityPlayer;
 import net.minecraft.init.SoundEvents;
+import net.minecraft.inventory.InventoryHelper;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemStack;
 import net.minecraft.tileentity.TileEntity;
@@ -50,6 +51,8 @@ public class TBAlloyFurnaceBlock
 
 	public static final PropertyDirection FACING = BlockHorizontal.FACING;
 
+	private static boolean keepInventory = false;
+
 	public final boolean active;
 
 
@@ -79,6 +82,32 @@ public class TBAlloyFurnaceBlock
 	}
 
 
+	public static void setState( final boolean burning , final World world , final BlockPos pos )
+	{
+		final IBlockState iblockstate = world.getBlockState( pos );
+		final TileEntity tileentity = world.getTileEntity( pos );
+
+		TBAlloyFurnaceBlock.keepInventory = true;
+		if ( burning ) {
+			world.setBlockState( pos , TechBase.ALLOY_FURNACE_BLOCK_ACTIVE.getDefaultState( ).withProperty(
+					TBAlloyFurnaceBlock.FACING , iblockstate.getValue( TBAlloyFurnaceBlock.FACING ) ) , 3 );
+			world.setBlockState( pos , TechBase.ALLOY_FURNACE_BLOCK_ACTIVE.getDefaultState( ).withProperty(
+					TBAlloyFurnaceBlock.FACING , iblockstate.getValue( TBAlloyFurnaceBlock.FACING ) ) , 3 );
+		} else {
+			world.setBlockState( pos , TechBase.ALLOY_FURNACE_BLOCK_INACTIVE.getDefaultState( ).withProperty(
+					TBAlloyFurnaceBlock.FACING , iblockstate.getValue( TBAlloyFurnaceBlock.FACING ) ) , 3 );
+			world.setBlockState( pos , TechBase.ALLOY_FURNACE_BLOCK_INACTIVE.getDefaultState( ).withProperty(
+					TBAlloyFurnaceBlock.FACING , iblockstate.getValue( TBAlloyFurnaceBlock.FACING ) ) , 3 );
+		}
+		TBAlloyFurnaceBlock.keepInventory = false;
+
+		if ( tileentity != null ) {
+			tileentity.validate( );
+			world.setTileEntity( pos , tileentity );
+		}
+	}
+
+
 	// *************************************************************************************************
 	// RENDERING AND COLLISIONS
 	// *************************************************************************************************
@@ -265,15 +294,34 @@ public class TBAlloyFurnaceBlock
 	// *************************************************************************************************
 
 	@Override
-	public boolean onBlockActivated( World worldIn , BlockPos pos , IBlockState state , EntityPlayer playerIn ,
-			EnumHand hand , ItemStack heldItem , EnumFacing side , float hitX , float hitY , float hitZ )
+	public boolean onBlockActivated( final World worldIn , final BlockPos pos , final IBlockState state ,
+			final EntityPlayer playerIn , final EnumHand hand , final ItemStack heldItem , final EnumFacing side ,
+			final float hitX , final float hitY , final float hitZ )
 	{
-		TileEntity te = worldIn.getTileEntity( pos );
-		if ( !(te instanceof TBAlloyFurnaceTileEntity ) || playerIn.isSneaking( ) ) {
+		final TileEntity te = worldIn.getTileEntity( pos );
+		if ( ! ( te instanceof TBAlloyFurnaceTileEntity ) || playerIn.isSneaking( ) ) {
 			return false;
 		}
 		playerIn.openGui( Mmm.get( ) , 0 , worldIn , pos.getX( ) , pos.getY( ) , pos.getZ( ) );
 		return true;
 	}
 
+
+	@Override
+	public void breakBlock( final World worldIn , final BlockPos pos , final IBlockState state )
+	{
+		if ( !TBAlloyFurnaceBlock.keepInventory ) {
+			final TileEntity tileEntity = worldIn.getTileEntity( pos );
+
+			if ( tileEntity instanceof TBAlloyFurnaceTileEntity ) {
+				final TBAlloyFurnaceTileEntity afte = (TBAlloyFurnaceTileEntity) tileEntity;
+				InventoryHelper.dropInventoryItems( worldIn , pos , afte.input );
+				InventoryHelper.dropInventoryItems( worldIn , pos , afte.fuel );
+				InventoryHelper.dropInventoryItems( worldIn , pos , afte.output );
+			}
+		}
+
+		super.breakBlock( worldIn , pos , state );
+	}
+
 }
diff --git a/src/java/mmm/tech/base/TBAlloyFurnaceGui.java b/src/java/mmm/tech/base/TBAlloyFurnaceGui.java
index 2e0303e..574f50f 100644
--- a/src/java/mmm/tech/base/TBAlloyFurnaceGui.java
+++ b/src/java/mmm/tech/base/TBAlloyFurnaceGui.java
@@ -12,6 +12,7 @@ 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.tileentity.TileEntity;
 import net.minecraft.util.ResourceLocation;
 import net.minecraftforge.fml.relauncher.Side;
 import net.minecraftforge.fml.relauncher.SideOnly;
@@ -194,6 +195,27 @@ public class TBAlloyFurnaceGui
 		this.mc.getTextureManager( ).bindTexture( TBAlloyFurnaceGui.TEXTURES[ this.selectedTab.ordinal( ) ] );
 		this.drawTexturedModalRect( this.guiLeft , this.guiTop , 0 , 0 , this.xSize , this.ySize );
 
+		if ( this.selectedTab == Tab.MAIN ) {
+			int x = ( this.width - this.xSize ) / 2;
+			int y = ( this.height - this.ySize ) / 2;
+
+			TileEntity atPos = this.container.world.getTileEntity( this.container.position );
+			if ( atPos instanceof TBAlloyFurnaceTileEntity ) {
+				TBAlloyFurnaceTileEntity te = (TBAlloyFurnaceTileEntity) atPos;
+				// Burn
+				if ( te.isBurning( ) ) {
+					int burn = te.getBurnProgress( 13 );
+					this.drawTexturedModalRect( x + 89 , y + 38 + 13 - burn , 176 , 28 - burn , 14 , burn + 1 );
+				}
+
+				// Alloying progress
+				if ( te.isAlloying( ) ) {
+					int alloy = te.getAlloyingProgress( 47 );
+					this.drawTexturedModalRect( x + 73 , y + 17 , 176 , 29 , alloy + 1 , 16 );
+				}
+			}
+		}
+
 		// Active tab
 		this.mc.getTextureManager( ).bindTexture( TBAlloyFurnaceGui.TEXTURES[ 0 ] );
 		this.drawTab( this.selectedTab );
diff --git a/src/java/mmm/tech/base/TBAlloyFurnaceTileEntity.java b/src/java/mmm/tech/base/TBAlloyFurnaceTileEntity.java
index 9a8fa56..fc3ff26 100644
--- a/src/java/mmm/tech/base/TBAlloyFurnaceTileEntity.java
+++ b/src/java/mmm/tech/base/TBAlloyFurnaceTileEntity.java
@@ -4,10 +4,13 @@ package mmm.tech.base;
 import mmm.materials.MAlloyRecipe;
 import mmm.utils.UInventoryGrid;
 import net.minecraft.block.state.IBlockState;
+import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NBTTagCompound;
 import net.minecraft.network.NetworkManager;
 import net.minecraft.network.play.server.SPacketUpdateTileEntity;
 import net.minecraft.tileentity.TileEntity;
+import net.minecraft.tileentity.TileEntityFurnace;
+import net.minecraft.util.ITickable;
 import net.minecraft.util.ResourceLocation;
 import net.minecraftforge.common.util.Constants.NBT;
 
@@ -15,6 +18,7 @@ import net.minecraftforge.common.util.Constants.NBT;
 
 public class TBAlloyFurnaceTileEntity
 		extends TileEntity
+		implements ITickable
 {
 
 	public final UInventoryGrid input;
@@ -23,6 +27,11 @@ public class TBAlloyFurnaceTileEntity
 
 	public MAlloyRecipe recipe;
 
+	private MAlloyRecipe alloying;
+	private int alloyCurrent;
+	private int burnCurrent;
+	private int burnTotal;
+
 
 	public TBAlloyFurnaceTileEntity( )
 	{
@@ -52,7 +61,6 @@ public class TBAlloyFurnaceTileEntity
 		compound.setTag( "Fuel" , this.fuel.serializeNBT( ) );
 		compound.setTag( "Output" , this.output.serializeNBT( ) );
 		this.writeSyncData( compound );
-		System.err.println( "tag " + compound.toString( ) );
 		return compound;
 	}
 
@@ -86,6 +94,61 @@ public class TBAlloyFurnaceTileEntity
 	}
 
 
+	@Override
+	public void update( )
+	{
+		if ( this.worldObj.isRemote ) {
+			return;
+		}
+
+		final boolean wasBurning = this.isBurning( );
+		boolean dirty = false;
+
+		if ( wasBurning ) {
+			dirty = true;
+			this.burnCurrent--;
+
+			if ( this.alloying != null ) {
+				this.alloyCurrent--;
+				if ( this.alloyCurrent == 0 ) {
+					this.addOutput( );
+					if ( this.canAlloy( ) ) {
+						this.startAlloying( );
+					} else {
+						this.alloying = null;
+					}
+				}
+			}
+
+			if ( this.burnCurrent == 0 ) {
+				if ( this.alloying != null ) {
+					if ( !this.startBurning( this.alloyCurrent ) ) {
+						// XXX produce slag
+						this.alloying = null;
+						this.alloyCurrent = this.burnCurrent = this.burnTotal = 0;
+					}
+				} else {
+					this.burnTotal = this.burnCurrent = 0;
+				}
+			}
+
+		} else if ( this.canAlloy( ) && this.startBurning( this.recipe.burnTime ) ) {
+			this.startAlloying( );
+			dirty = true;
+		}
+
+		if ( this.isBurning( ) != wasBurning ) {
+			TBAlloyFurnaceBlock.setState( this.isBurning( ) , this.worldObj , this.pos );
+		}
+
+		if ( dirty ) {
+			this.markDirty( );
+			final IBlockState state = this.worldObj.getBlockState( this.pos );
+			this.worldObj.notifyBlockUpdate( this.pos , state , state , 3 );
+		}
+	}
+
+
 	public void setRecipe( final ResourceLocation location )
 	{
 		MAlloyRecipe recipe = MAlloyRecipe.REGISTRY.getRecipe( location );
@@ -106,6 +169,195 @@ public class TBAlloyFurnaceTileEntity
 	}
 
 
+	public boolean isBurning( )
+	{
+		return this.burnTotal != 0;
+	}
+
+
+	public int getBurnProgress( int max )
+	{
+		int t = this.burnTotal;
+		if ( t == 0 ) {
+			t = 200;
+		}
+		return Math.min( max , this.burnCurrent * max / t );
+	}
+
+
+	/**
+	 * Find the most appropriate fuel for the specified burn time.
+	 *
+	 * @param requiredBurnTime
+	 *            the burn time we need
+	 *
+	 * @return the fuel slot's index, or -1 if there is no fuel.
+	 */
+	private int getBestFuel( final int requiredBurnTime )
+	{
+		int bestStack = -1 , bestDiff = Integer.MAX_VALUE;
+		for ( int i = 0 ; i < this.fuel.slotsCount ; i++ ) {
+			final ItemStack fuel = this.fuel.inventoryContents[ i ];
+			if ( fuel == null || fuel.stackSize == 0 ) {
+				continue;
+			}
+			final int fuelBurnTime = TileEntityFurnace.getItemBurnTime( fuel );
+			final int diff = Math.abs( requiredBurnTime - fuelBurnTime );
+			if ( diff < bestDiff ) {
+				bestStack = i;
+				bestDiff = diff;
+			}
+		}
+		return bestStack;
+	}
+
+
+	private boolean startBurning( final int requiredBurnTime )
+	{
+		final int fuelSlot = this.getBestFuel( requiredBurnTime );
+		if ( fuelSlot == -1 ) {
+			return false;
+		}
+
+		final ItemStack fuelStack = this.fuel.inventoryContents[ fuelSlot ];
+		this.burnCurrent = this.burnTotal = TileEntityFurnace.getItemBurnTime( fuelStack );
+
+		fuelStack.stackSize--;
+		if ( fuelStack.stackSize == 0 ) {
+			final ItemStack replace = fuelStack.getItem( ).getContainerItem( fuelStack );
+			this.fuel.inventoryContents[ fuelSlot ] = replace;
+		}
+
+		return true;
+	}
+
+
+	/**
+	 * Checks if alloying is possible
+	 * <p>
+	 * In order to do that, we need to check that:
+	 * <ul>
+	 * <li>the current recipe's requirements are present in the input slot, AND
+	 * <li>either there is a free output slot OR there is an output slot with the current output
+	 * which can accept enough items.
+	 * </ul>
+	 *
+	 * @return <code>true</code> if alloying is possible
+	 */
+	public boolean canAlloy( )
+	{
+		if ( !this.recipe.checkInventory( this.input ) ) {
+			return false;
+		}
+
+		final ItemStack output = this.recipe.output;
+		final int fullStack = Math.min( this.output.getInventoryStackLimit( ) , output.getMaxStackSize( ) );
+		int freeSpace = 0;
+
+		for ( int i = 0 ; i < this.output.slotsCount && freeSpace < output.stackSize ; i++ ) {
+			final ItemStack outputSlot = this.output.inventoryContents[ i ];
+			if ( outputSlot == null ) {
+				freeSpace += fullStack;
+			} else if ( outputSlot.isItemEqual( output ) ) {
+				freeSpace += fullStack - outputSlot.stackSize;
+			}
+		}
+
+		return freeSpace >= output.stackSize;
+	}
+
+
+	public boolean isAlloying( )
+	{
+		return this.alloying != null;
+	}
+
+
+	public int getAlloyingProgress( int max )
+	{
+		if ( this.alloying == null ) {
+			return max;
+		}
+
+		int t = this.alloying.burnTime;
+		if ( t == 0 ) {
+			t = 200;
+		}
+		return Math.min( max , ( t - this.alloyCurrent ) * max / t );
+	}
+
+
+	private void startAlloying( )
+	{
+		this.removeRecipeInput( );
+		this.alloying = this.recipe;
+		this.alloyCurrent = this.recipe.burnTime;
+	}
+
+
+	private void removeRecipeInput( )
+	{
+		final ItemStack[] rIn = this.recipe.inputs;
+		final int inSlots = this.input.slotsCount;
+		for ( int i = 0 ; i < rIn.length ; i++ ) {
+			final ItemStack inputStack = rIn[ i ];
+			int found = 0;
+			for ( int slot = 0 ; slot < inSlots ; slot++ ) {
+				final ItemStack stackInSlot = this.input.inventoryContents[ slot ];
+				if ( stackInSlot == null || !inputStack.isItemEqual( stackInSlot ) ) {
+					continue;
+				}
+
+				final int take = Math.min( inputStack.stackSize - found , stackInSlot.stackSize );
+				found += take;
+				stackInSlot.stackSize -= take;
+				if ( stackInSlot.stackSize == 0 ) {
+					final ItemStack replace = stackInSlot.getItem( ).getContainerItem( stackInSlot );
+					this.input.inventoryContents[ slot ] = replace;
+				}
+
+				if ( found == inputStack.stackSize ) {
+					break;
+				}
+			}
+		}
+	}
+
+
+	private void addOutput( )
+	{
+		final ItemStack wanted = this.alloying.output;
+		final int maxStackSize = Math.min( this.output.getInventoryStackLimit( ) , wanted.getMaxStackSize( ) );
+		int added = 0;
+		for ( int i = 0 ; i < this.output.slotsCount ; i++ ) {
+			ItemStack outputStack = this.output.inventoryContents[ i ];
+			int canAdd;
+			if ( outputStack == null ) {
+				canAdd = maxStackSize;
+				outputStack = new ItemStack( wanted.getItem( ) , 0 , wanted.getItemDamage( ) );
+				this.output.inventoryContents[ i ] = outputStack;
+
+			} else if ( outputStack.isItemEqual( wanted ) ) {
+				canAdd = maxStackSize - outputStack.stackSize;
+
+			} else {
+				canAdd = 0;
+			}
+
+			if ( canAdd == 0 ) {
+				continue;
+			}
+
+			final int doAdd = Math.min( canAdd , wanted.stackSize - added );
+			added += doAdd;
+			outputStack.stackSize += doAdd;
+			if ( added == wanted.stackSize ) {
+				break;
+			}
+		}
+	}
+
+
 	private void readSyncData( final NBTTagCompound compound )
 	{
 		final String recipeName = compound.getString( "Recipe" );
@@ -113,12 +365,35 @@ public class TBAlloyFurnaceTileEntity
 		if ( this.recipe == null ) {
 			this.recipe = MAlloyRecipe.REGISTRY.getRecipes( ).get( 0 );
 		}
+
+		this.burnCurrent = compound.getInteger( "BurnCurrent" );
+		this.burnTotal = compound.getInteger( "BurnTotal" );
+
+		final String alloyingRecipeName = compound.getString( "AlloyRecipe" );
+		if ( "".equals( alloyingRecipeName ) ) {
+			this.alloying = null;
+		} else {
+			this.alloying = MAlloyRecipe.REGISTRY.getRecipe( new ResourceLocation( recipeName ) );
+		}
+		if ( this.alloying == null ) {
+			this.alloyCurrent = 0;
+		} else {
+			this.alloyCurrent = compound.getInteger( "AlloyCurrent" );
+		}
 	}
 
 
 	private void writeSyncData( final NBTTagCompound compound )
 	{
 		compound.setString( "Recipe" , this.recipe.name.toString( ) );
+		if ( this.alloying != null ) {
+			compound.setString( "AlloyRecipe" , this.alloying.name.toString( ) );
+			compound.setInteger( "AlloyCurrent" , this.alloyCurrent );
+		}
+		if ( this.burnTotal != 0 ) {
+			compound.setInteger( "BurnTotal" , this.burnTotal );
+			compound.setInteger( "BurnCurrent" , this.burnCurrent );
+		}
 	}
 
 }
diff --git a/src/java/mmm/tech/base/TechBase.java b/src/java/mmm/tech/base/TechBase.java
index 87c73d9..92317cf 100644
--- a/src/java/mmm/tech/base/TechBase.java
+++ b/src/java/mmm/tech/base/TechBase.java
@@ -32,7 +32,7 @@ public class TechBase
 		URegistry.addServerMessage( TBAlloyFurnaceMessage.class );
 
 		// FIXME test, remove this later
-		MAlloyRecipe.build( ).setName( "test" ).setBurnTime( 200 ).setExperience( .05f )
+		MAlloyRecipe.build( ).setName( "test" ).setBurnTime( 50 ).setExperience( .05f )
 				.setOutput( Items.COOKED_CHICKEN , 10 ).addInput( Items.COOKED_BEEF ).addInput( Items.COOKED_PORKCHOP )
 				.register( );
 	}
diff --git a/src/java/mmm/utils/UInventoryGrid.java b/src/java/mmm/utils/UInventoryGrid.java
index a6eabe8..763cb48 100644
--- a/src/java/mmm/utils/UInventoryGrid.java
+++ b/src/java/mmm/utils/UInventoryGrid.java
@@ -24,7 +24,7 @@ public class UInventoryGrid
 	public final int width;
 	public final int height;
 	public final int slotsCount;
-	private final ItemStack[] inventoryContents;
+	public final ItemStack[] inventoryContents;
 
 
 	public UInventoryGrid( final String name , final int width , final int height )