From 88de097c1c506c2f6df9676dc6148d0fbf1ba3cd Mon Sep 17 00:00:00 2001
From: SquidDev <bonzoweb@hotmail.co.uk>
Date: Thu, 11 May 2017 18:19:34 +0100
Subject: [PATCH] Add more general item colouring system

This allows for other items, such as turtles, to be dyed in the future.
This also adds support for the ore dictionary, meaning you can use other
mod's dyes to colour items.
---
 .../proxy/ComputerCraftProxyClient.java       |  29 ++--
 .../shared/common/ColourableRecipe.java       | 106 ++++++++++++++
 .../shared/common/IColouredItem.java          |  10 ++
 .../shared/media/items/ItemDiskExpanded.java  |  16 ++-
 .../shared/media/items/ItemDiskLegacy.java    |  12 +-
 .../shared/media/recipes/DiskRecipe.java      | 131 +++++-------------
 .../proxy/ComputerCraftProxyCommon.java       |   5 +
 .../shared/util/ColourTracker.java            |  48 +++++++
 .../shared/util/ColourUtils.java              |  37 +++++
 9 files changed, 274 insertions(+), 120 deletions(-)
 create mode 100644 src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java
 create mode 100644 src/main/java/dan200/computercraft/shared/common/IColouredItem.java
 create mode 100644 src/main/java/dan200/computercraft/shared/util/ColourTracker.java
 create mode 100644 src/main/java/dan200/computercraft/shared/util/ColourUtils.java

diff --git a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java
index 7adb8e3ff..faa65bb6a 100644
--- a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java
+++ b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java
@@ -47,7 +47,6 @@ import net.minecraftforge.client.event.RenderGameOverlayEvent;
 import net.minecraftforge.client.event.RenderHandEvent;
 import net.minecraftforge.client.event.RenderPlayerEvent;
 import net.minecraftforge.client.model.ModelLoader;
-import net.minecraftforge.client.model.ModelLoaderRegistry;
 import net.minecraftforge.common.MinecraftForge;
 import net.minecraftforge.fml.client.FMLClientHandler;
 import net.minecraftforge.fml.client.registry.ClientRegistry;
@@ -515,20 +514,20 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
         }
     }
 
-	@SideOnly(Side.CLIENT)
-	private static class DiskColorHandler implements IItemColor
-	{
-		private final ItemDiskLegacy disk;
+    @SideOnly(Side.CLIENT)
+    private static class DiskColorHandler implements IItemColor
+    {
+        private final ItemDiskLegacy disk;
 
-		private DiskColorHandler(ItemDiskLegacy disk)
-		{
-			this.disk = disk;
-		}
+        private DiskColorHandler( ItemDiskLegacy disk )
+        {
+            this.disk = disk;
+        }
 
-		@Override
-		public int getColorFromItemstack( @Nonnull ItemStack stack, int layer)
-		{
-			return layer == 0 ? 0xFFFFFF : disk.getColor(stack);
-		}
-	}
+        @Override
+        public int getColorFromItemstack( @Nonnull ItemStack stack, int layer )
+        {
+            return layer == 0 ? 0xFFFFFF : disk.getColour( stack );
+        }
+    }
 }
diff --git a/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java b/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java
new file mode 100644
index 000000000..ce59b7fc5
--- /dev/null
+++ b/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java
@@ -0,0 +1,106 @@
+package dan200.computercraft.shared.common;
+
+import dan200.computercraft.shared.util.Colour;
+import dan200.computercraft.shared.util.ColourTracker;
+import dan200.computercraft.shared.util.ColourUtils;
+import net.minecraft.inventory.InventoryCrafting;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.crafting.IRecipe;
+import net.minecraft.world.World;
+import net.minecraftforge.common.ForgeHooks;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public class ColourableRecipe implements IRecipe
+{
+    @Override
+    public boolean matches( @Nonnull InventoryCrafting inv, @Nonnull World worldIn )
+    {
+        boolean hasColourable = false;
+        boolean hasDye = false;
+        for( int i = 0; i < inv.getSizeInventory(); i++ )
+        {
+            ItemStack stack = inv.getStackInSlot( i );
+            if( stack == null ) continue;
+
+            if( stack.getItem() instanceof IColouredItem )
+            {
+                if( hasColourable ) return false;
+                hasColourable = true;
+            }
+            else if( ColourUtils.getStackColour( stack ) >= 0 )
+            {
+                hasDye = true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        return hasColourable && hasDye;
+    }
+
+    @Nullable
+    @Override
+    public ItemStack getCraftingResult( @Nonnull InventoryCrafting inv )
+    {
+        ItemStack colourable = null;
+
+        ColourTracker tracker = new ColourTracker();
+
+        for( int i = 0; i < inv.getSizeInventory(); ++i )
+        {
+            ItemStack stack = inv.getStackInSlot( i );
+
+            if( stack == null ) continue;
+
+            if( stack.getItem() instanceof IColouredItem )
+            {
+                colourable = stack;
+            }
+            else
+            {
+                int index = ColourUtils.getStackColour( stack );
+                if( index < 0 ) continue;
+
+                Colour colour = Colour.values()[ index ];
+                tracker.addColour( colour.getR(), colour.getG(), colour.getB() );
+            }
+        }
+
+        if( colourable == null )
+        {
+            return null;
+        }
+
+        return ((IColouredItem) colourable.getItem()).setColour( colourable, tracker.getColour() );
+    }
+
+    @Override
+    public int getRecipeSize()
+    {
+        return 2;
+    }
+
+    @Nullable
+    @Override
+    public ItemStack getRecipeOutput()
+    {
+        return null;
+    }
+
+    @Nonnull
+    @Override
+    public ItemStack[] getRemainingItems( @Nonnull InventoryCrafting inv )
+    {
+        ItemStack[] results = new ItemStack[ inv.getSizeInventory() ];
+        for( int i = 0; i < results.length; ++i )
+        {
+            ItemStack stack = inv.getStackInSlot( i );
+            results[ i ] = ForgeHooks.getContainerItem( stack );
+        }
+        return results;
+    }
+}
diff --git a/src/main/java/dan200/computercraft/shared/common/IColouredItem.java b/src/main/java/dan200/computercraft/shared/common/IColouredItem.java
new file mode 100644
index 000000000..fb28403bd
--- /dev/null
+++ b/src/main/java/dan200/computercraft/shared/common/IColouredItem.java
@@ -0,0 +1,10 @@
+package dan200.computercraft.shared.common;
+
+import net.minecraft.item.ItemStack;
+
+public interface IColouredItem
+{
+    int getColour( ItemStack stack );
+
+    ItemStack setColour( ItemStack stack, int colour );
+}
diff --git a/src/main/java/dan200/computercraft/shared/media/items/ItemDiskExpanded.java b/src/main/java/dan200/computercraft/shared/media/items/ItemDiskExpanded.java
index 02df3b622..ab3c10330 100644
--- a/src/main/java/dan200/computercraft/shared/media/items/ItemDiskExpanded.java
+++ b/src/main/java/dan200/computercraft/shared/media/items/ItemDiskExpanded.java
@@ -44,7 +44,7 @@ public class ItemDiskExpanded extends ItemDiskLegacy
         return -1;
     }
 
-    @Override    
+    @Override
     protected void setDiskID( ItemStack stack, int id )
     {
         if( id >= 0 )
@@ -58,10 +58,18 @@ public class ItemDiskExpanded extends ItemDiskLegacy
             nbt.setInteger( "diskID", id );
         }
     }
-    
-    public int getColor( ItemStack stack )
+
+    @Override
+    public int getColour( ItemStack stack )
     {
         NBTTagCompound nbt = stack.getTagCompound();
-           return nbt != null && nbt.hasKey("color") ? nbt.getInteger("color") : Colour.values()[ Math.min(15, stack.getItemDamage()) ].getHex();
+        if( nbt != null && nbt.hasKey( "color" ) )
+        {
+            return nbt.getInteger( "color" );
+        }
+        else
+        {
+            return Colour.values()[ Math.min( 15, stack.getItemDamage() ) ].getHex();
+        }
     }
 }
diff --git a/src/main/java/dan200/computercraft/shared/media/items/ItemDiskLegacy.java b/src/main/java/dan200/computercraft/shared/media/items/ItemDiskLegacy.java
index 6b5c3f0f5..05581bf54 100644
--- a/src/main/java/dan200/computercraft/shared/media/items/ItemDiskLegacy.java
+++ b/src/main/java/dan200/computercraft/shared/media/items/ItemDiskLegacy.java
@@ -10,6 +10,7 @@ import dan200.computercraft.ComputerCraft;
 import dan200.computercraft.api.ComputerCraftAPI;
 import dan200.computercraft.api.filesystem.IMount;
 import dan200.computercraft.api.media.IMedia;
+import dan200.computercraft.shared.common.IColouredItem;
 import dan200.computercraft.shared.util.Colour;
 import net.minecraft.creativetab.CreativeTabs;
 import net.minecraft.entity.player.EntityPlayer;
@@ -24,7 +25,7 @@ import javax.annotation.Nonnull;
 import java.util.List;
 
 public class ItemDiskLegacy extends Item
-    implements IMedia
+    implements IMedia, IColouredItem
 {
     public ItemDiskLegacy()
     {
@@ -142,7 +143,8 @@ public class ItemDiskLegacy extends Item
         return ComputerCraftAPI.createSaveDirMount( world, "computer/disk/" + diskID, ComputerCraft.floppySpaceLimit );
     }
 
-    public int getColor( ItemStack stack )
+    @Override
+    public int getColour( ItemStack stack )
     {
         return Colour.Blue.getHex();
     }
@@ -152,4 +154,10 @@ public class ItemDiskLegacy extends Item
     {
         return true;
     }
+
+    @Override
+    public ItemStack setColour( ItemStack stack, int colour )
+    {
+        return ItemDiskExpanded.createFromIDAndColour( getDiskID( stack ), getLabel( stack ), colour );
+    }
 }
diff --git a/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java b/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java
index 6a9191fd7..35bffc4f4 100644
--- a/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java
+++ b/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java
@@ -8,6 +8,8 @@ package dan200.computercraft.shared.media.recipes;
 
 import dan200.computercraft.shared.media.items.ItemDiskLegacy;
 import dan200.computercraft.shared.util.Colour;
+import dan200.computercraft.shared.util.ColourTracker;
+import dan200.computercraft.shared.util.ColourUtils;
 import net.minecraft.init.Items;
 import net.minecraft.inventory.InventoryCrafting;
 import net.minecraft.item.ItemStack;
@@ -18,129 +20,60 @@ import javax.annotation.Nonnull;
 
 public class DiskRecipe implements IRecipe
 {
-    public DiskRecipe()
-    {
-    }
-
     @Override
-    public boolean matches( @Nonnull InventoryCrafting inventory, @Nonnull World world )
+    public boolean matches( @Nonnull InventoryCrafting inv, @Nonnull World world )
     {
-        boolean diskFound = false;
         boolean paperFound = false;
         boolean redstoneFound = false;
-        boolean dyeFound = false;
 
-        for (int var5 = 0; var5 < inventory.getSizeInventory(); ++var5)
+        for( int i = 0; i < inv.getSizeInventory(); ++i )
         {
-            ItemStack var6 = inventory.getStackInSlot(var5);
+            ItemStack stack = inv.getStackInSlot( i );
 
-            if (var6 != null)
+            if( stack != null )
             {
-                if (var6.getItem() instanceof ItemDiskLegacy )
+                if( stack.getItem() == Items.PAPER )
                 {
-                    if (diskFound || redstoneFound || paperFound) // make sure no redstone or paper already accepted if disk there
-                    {
-                        return false;
-                    }
-
-                    diskFound = true;
-                }
-                else if( var6.getItem() == Items.DYE )
-                {
-                    dyeFound = true;
-                }
-                else if( var6.getItem() == Items.PAPER )
-                {
-                    if(paperFound || diskFound)
-                    {
-                        return false;
-                    }
+                    if( paperFound ) return false;
                     paperFound = true;
                 }
-                else if (var6.getItem() == Items.REDSTONE)
+                else if( stack.getItem() == Items.REDSTONE )
                 {
-                    if (redstoneFound || diskFound)
-                    {
-                        return false;
-                    }
-                    
+                    if( redstoneFound ) return false;
                     redstoneFound = true;
                 }
-                else
+                else if( ColourUtils.getStackColour( stack ) < 0 )
                 {
                     return false;
                 }
             }
         }
-        
-        return (redstoneFound && paperFound) || (diskFound && dyeFound);
+
+        return redstoneFound && paperFound;
     }
 
     @Override
-    public ItemStack getCraftingResult( @Nonnull InventoryCrafting par1InventoryCrafting)
+    public ItemStack getCraftingResult( @Nonnull InventoryCrafting inv )
     {
-        int diskID = -1;
-        String diskLabel = null;
+        ColourTracker tracker = new ColourTracker();
 
-        int[] var3 = new int[3];
-        int var4 = 0;
-        int var5 = 0;
-        ItemDiskLegacy var6;
-        int var7;
-        int var9;
-        float var10;
-        float var11;
-        int var17;
-        boolean dyeFound = false;
-
-        for (var7 = 0; var7 < par1InventoryCrafting.getSizeInventory(); ++var7)
+        for( int i = 0; i < inv.getSizeInventory(); ++i )
         {
-            ItemStack var8 = par1InventoryCrafting.getStackInSlot(var7);
+            ItemStack stack = inv.getStackInSlot( i );
 
-            if (var8 != null)
+            if( stack == null ) continue;
+
+            if( stack.getItem() != Items.PAPER && stack.getItem() != Items.REDSTONE )
             {
-                if (var8.getItem() instanceof ItemDiskLegacy )
-                {
-                    var6 = (ItemDiskLegacy)var8.getItem();
-                    diskID = var6.getDiskID( var8 );
-                    diskLabel = var6.getLabel( var8 );
-                }
-                else if (var8.getItem() == Items.DYE)
-                {
-                    dyeFound = true;
-                    float[] var14 = Colour.values()[ var8.getItemDamage() & 0xf ].getRGB();
-                    int var16 = (int)(var14[0] * 255.0F);
-                    int var15 = (int)(var14[1] * 255.0F);
-                    var17 = (int)(var14[2] * 255.0F);
-                    var4 += Math.max(var16, Math.max(var15, var17));
-                    var3[0] += var16;
-                    var3[1] += var15;
-                    var3[2] += var17;                 
-                    ++var5;
-                }
-                else if (!(var8.getItem() != Items.PAPER || var8.getItem() != Items.REDSTONE))
-                {
-                    return null;
-                }
+                int index = ColourUtils.getStackColour( stack );
+                if( index < 0 ) continue;
+
+                Colour colour = Colour.values()[ index ];
+                tracker.addColour( colour.getR(), colour.getG(), colour.getB() );
             }
         }
-        
-        if( !dyeFound )
-        {
-            return ItemDiskLegacy.createFromIDAndColour( diskID, diskLabel, Colour.Blue.getHex() );
-        }
-        
-        var7 = var3[0] / var5;
-        int var13 = var3[1] / var5;
-        var9 = var3[2] / var5;
-        var10 = (float)var4 / (float)var5;
-        var11 = (float)Math.max(var7, Math.max(var13, var9));
-        var7 = (int)((float)var7 * var10 / var11);
-        var13 = (int)((float)var13 * var10 / var11);
-        var9 = (int)((float)var9 * var10 / var11);
-        var17 = (var7 << 8) + var13;
-        var17 = (var17 << 8) + var9;
-        return ItemDiskLegacy.createFromIDAndColour( diskID, diskLabel, var17 );
+
+        return ItemDiskLegacy.createFromIDAndColour( -1, null, tracker.hasColour() ? tracker.getColour() : Colour.Blue.getHex() );
     }
 
     @Override
@@ -157,13 +90,13 @@ public class DiskRecipe implements IRecipe
 
     @Nonnull
     @Override
-    public ItemStack[] getRemainingItems( @Nonnull InventoryCrafting inventoryCrafting )
+    public ItemStack[] getRemainingItems( @Nonnull InventoryCrafting inv )
     {
-        ItemStack[] results = new ItemStack[ inventoryCrafting.getSizeInventory() ];
-        for (int i = 0; i < results.length; ++i)
+        ItemStack[] results = new ItemStack[ inv.getSizeInventory() ];
+        for( int i = 0; i < results.length; ++i )
         {
-            ItemStack stack = inventoryCrafting.getStackInSlot(i);
-            results[i] = net.minecraftforge.common.ForgeHooks.getContainerItem(stack);
+            ItemStack stack = inv.getStackInSlot( i );
+            results[ i ] = net.minecraftforge.common.ForgeHooks.getContainerItem( stack );
         }
         return results;
     }
diff --git a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java
index 749abf8e8..a4eb6cdf3 100644
--- a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java
+++ b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java
@@ -10,6 +10,7 @@ import dan200.computercraft.ComputerCraft;
 import dan200.computercraft.api.ComputerCraftAPI;
 import dan200.computercraft.api.pocket.IPocketUpgrade;
 import dan200.computercraft.core.computer.MainThread;
+import dan200.computercraft.shared.common.ColourableRecipe;
 import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
 import dan200.computercraft.shared.common.TileGeneric;
 import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
@@ -276,6 +277,7 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
         RecipeSorter.register( "computercraft:impostor", ImpostorRecipe.class, RecipeSorter.Category.SHAPED, "after:minecraft:shapeless" );
         RecipeSorter.register( "computercraft:impostor_shapeless", ImpostorShapelessRecipe.class, RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless" );
         RecipeSorter.register( "computercraft:disk", DiskRecipe.class, RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless" );
+        RecipeSorter.register( "computercraft:colour", ColourableRecipe.class, RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless" );
         RecipeSorter.register( "computercraft:printout", PrintoutRecipe.class, RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless" );
         RecipeSorter.register( "computercraft:pocket_computer_upgrade", PocketComputerUpgradeRecipe.class, RecipeSorter.Category.SHAPELESS, "after:minecraft:shapeless" );
 
@@ -375,6 +377,9 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
         // Disk
         GameRegistry.addRecipe( new DiskRecipe() );
 
+        // Colourable items (turtles, disks)
+        GameRegistry.addRecipe( new ColourableRecipe() );
+
         // Impostor Disk recipes (to fool NEI)
         ItemStack paper = new ItemStack( Items.PAPER, 1 );
         ItemStack redstone = new ItemStack( Items.REDSTONE, 1 );
diff --git a/src/main/java/dan200/computercraft/shared/util/ColourTracker.java b/src/main/java/dan200/computercraft/shared/util/ColourTracker.java
new file mode 100644
index 000000000..7be0ad300
--- /dev/null
+++ b/src/main/java/dan200/computercraft/shared/util/ColourTracker.java
@@ -0,0 +1,48 @@
+package dan200.computercraft.shared.util;
+
+/**
+ * A reimplementation of the colour system in {@link net.minecraft.item.crafting.RecipesArmorDyes}, but
+ * bundled together as an object.
+ */
+public class ColourTracker
+{
+    private int total;
+    private int totalR;
+    private int totalG;
+    private int totalB;
+    private int count;
+
+    public void addColour( int r, int g, int b )
+    {
+        total += Math.max( r, Math.max( g, b ) );
+        totalR += r;
+        totalG += g;
+        totalB += b;
+        count++;
+    }
+
+    public void addColour( float r, float g, float b )
+    {
+        addColour( (int) (r * 255), (int) (g * 255), (int) (b * 255) );
+    }
+
+    public boolean hasColour()
+    {
+        return count > 0;
+    }
+
+    public int getColour()
+    {
+        int avgR = totalR / count;
+        int avgG = totalG / count;
+        int avgB = totalB / count;
+
+        float avgTotal = (float) total / (float) count;
+        float avgMax = (float) Math.max( avgR, Math.max( avgG, avgB ) );
+        avgR = (int) (avgR * avgTotal / avgMax);
+        avgG = (int) (avgG * avgTotal / avgMax);
+        avgB = (int) (avgB * avgTotal / avgMax);
+
+        return (avgR << 16) | (avgG << 8) | avgB;
+    }
+}
diff --git a/src/main/java/dan200/computercraft/shared/util/ColourUtils.java b/src/main/java/dan200/computercraft/shared/util/ColourUtils.java
new file mode 100644
index 000000000..020a37724
--- /dev/null
+++ b/src/main/java/dan200/computercraft/shared/util/ColourUtils.java
@@ -0,0 +1,37 @@
+package dan200.computercraft.shared.util;
+
+import net.minecraft.item.ItemStack;
+import net.minecraftforge.oredict.OreDictionary;
+import org.apache.commons.lang3.ArrayUtils;
+
+public class ColourUtils
+{
+    private static final String[] DYES = new String[] {
+        "dyeBlack", "dyeRed", "dyeGreen", "dyeBrown",
+        "dyeBlue", "dyePurple", "dyeCyan", "dyeLightGray",
+        "dyeGray", "dyePink", "dyeLime", "dyeYellow",
+        "dyeLightBlue", "dyeMagenta", "dyeOrange", "dyeWhite"
+    };
+
+    private static int[] ids;
+
+    public static int getStackColour( ItemStack stack )
+    {
+        if( ids == null )
+        {
+            int ids[] = ColourUtils.ids = new int[ DYES.length ];
+            for( int i = 0; i < DYES.length; i++ )
+            {
+                ids[ i ] = OreDictionary.getOreID( DYES[ i ] );
+            }
+        }
+
+        for( int id : OreDictionary.getOreIDs( stack ) )
+        {
+            int index = ArrayUtils.indexOf( ids, id );
+            if( index >= 0 ) return index;
+        }
+
+        return -1;
+    }
+}