package ttftcuts.physis.common.item.material; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import com.google.common.collect.ImmutableList; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import ttftcuts.physis.Physis; import ttftcuts.physis.client.render.item.RenderSocketed; import ttftcuts.physis.common.helper.TextureHelper; import ttftcuts.physis.common.helper.recipe.IRecipeComponentTranslator; import ttftcuts.physis.common.helper.recipe.RecipeHelper; import ttftcuts.physis.common.helper.recipe.RecipeListGetter; import ttftcuts.physis.utils.ModFinder; import net.minecraft.item.Item; import net.minecraft.item.ItemTool; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraftforge.oredict.OreDictionary; public class PhysisToolMaterial { public static final String MATERIALTAG = "physisMaterial"; // list of ore dict names considered vanilla - will not consider mod items when getting colours public static final ImmutableList<String> vanillaOreNames = ImmutableList.of("logWood", "plankWood", "ingotIron", "ingotGold", "gemDiamond", "gemEmerald", "gemQuartz", "blockQuartz", "stone", "cobblestone", "sandstone", "blockGlass"); // list of mods excluded from colour calculation because of issues (e.g. GT items come up as grey) public static final ImmutableList<String> excludedMods = ImmutableList.of("gregtech"); public static Map<String,PhysisToolMaterial> materials; private static List<PhysisToolMaterial> materialsById; public static final int TINTS = 10; private static int[] defaultTints; public static boolean generated = false; private static int nextId = 0; public final int id; public String orename; public String orematerial; public String stickorename; public ItemStack ingot; public ItemStack stick; public ItemStack pick; public ItemTool pickitem; public Item.ToolMaterial toolmaterial; public int maxdamage; public boolean hastint = false; public Map<ItemStack, int[]> intermediateTints = new HashMap<ItemStack, int[]>(); public int[] tints; public int shafttint; public Object sourceRecipe; public PhysisToolMaterial(String orename, ItemStack ingot, String stickorename, ItemStack stick, ItemStack pick, Object source) { //Physis.logger.info("Registering material for "+orename+" with ingot "+ingot+", stick "+stick+" and pick "+pick); this.id = nextId; materialsById.add(this); nextId++; this.orename = orename; this.orematerial = orename.replaceFirst("[^A-Z]*(?=[A-Z])", ""); this.ingot = ingot; this.stick = stick; this.stickorename = stickorename; this.pick = pick; this.pickitem = (ItemTool)(pick.getItem()); this.toolmaterial = this.pickitem.func_150913_i(); this.maxdamage = this.toolmaterial.getMaxUses(); this.sourceRecipe = source; } public String getMaterialName() { return this.orematerial; } public int[] getHeadTints() { if (this.hastint) { return this.tints; } buildTintData(this); if (this.hastint) { return this.tints; } return defaultTints; } public int getShaftTint() { if (this.hastint) { return this.shafttint; } return 0x808080; } @SuppressWarnings("unchecked") public static void buildMaterials() { //Physis.logger.info("Building tool material list"); materials = new HashMap<String, PhysisToolMaterial>(); materialsById = new ArrayList<PhysisToolMaterial>(); //Physis.logger.info("Getting Ingots"); //String[] orenames = OreDictionary.getOreNames(); /*List<String> ingottypes = new ArrayList<String>(); for(String name : orenames) { if (name.startsWith("ingot")) { ingottypes.add(name); //Physis.logger.info(name); } }*/ //Physis.logger.info("Searching for picks"); List<ItemTool> picks = new ArrayList<ItemTool>(); Iterator<Item> ir = Item.itemRegistry.iterator(); String pickclass = "pickaxe"; while(ir.hasNext()) { Item item = ir.next(); if (item instanceof ItemTool) { ItemTool tool = (ItemTool)item; //Physis.logger.info("Tool: "+tool.getUnlocalizedName()); ItemStack toolstack = new ItemStack(tool); Set<String> toolclasses = tool.getToolClasses(toolstack); //Physis.logger.info(toolclasses); if (toolclasses.contains(pickclass)) { picks.add(tool); //Physis.logger.info("Pick: "+tool.getUnlocalizedName()); } } } //Physis.logger.info("Cross-checking materials"); for (RecipeListGetter list : RecipeHelper.recipeLists) { Iterator<?> iter = list.getIterator(); while(iter.hasNext()) { Object recipe = iter.next(); IRecipeComponentTranslator translator = RecipeHelper.getTranslatorForRecipe(list, recipe); if (translator == null || translator.getRecipeOutput(recipe) == null || translator.getRecipeOutput(recipe).getItem() == null) { continue; } ItemStack output = translator.getRecipeOutput(recipe); Item out = output.getItem(); for(ItemTool pick : picks) { if (out == pick) { ItemStack[] comp = null; boolean stickore = false; comp = translator.getRecipeComponents(recipe); stickore = translator.hasOreDictStick(); if (comp != null && comp.length == 9) { if (comp[0] != null && comp[1] != null && comp[2] != null && comp[4] != null && comp[7] != null) { // looks pick shaped to me! ItemStack stickitem = comp[4]; ItemStack otherstick = comp[7]; if (!compareStacks(stickitem, otherstick)) { // but the sticks don't match continue; } // stick processing String stickorename = null; if (stickore) { int[] stickoreids = OreDictionary.getOreIDs(stickitem); if (stickoreids.length > 0) { stickorename = OreDictionary.getOreName(stickoreids[0]); } } // head processing ItemStack[] head = { comp[0], comp[1], comp[2] }; String orename = ""; List<ItemStack> candidates = new ArrayList<ItemStack>(); for(int i=0; i<3; i++) { ItemStack h = head[i]; int[] oreids = OreDictionary.getOreIDs(h); if (oreids.length > 0) { candidates.add(h); } } ItemStack oreitem = null; if (candidates.size() == 0) { continue; } else { if (candidates.contains(head[1])) { if (candidates.size() == 3) { if (compareStacks(head[0], head[2])) { oreitem = head[0]; } else { oreitem = head[1]; } } } else { oreitem = candidates.get(0); } } if (oreitem != null) { orename = OreDictionary.getOreName(OreDictionary.getOreIDs(oreitem)[0]); } else { continue; } if(materials.containsKey(orename)) { if (materials.get(orename).stickorename == null && stickorename != null) { materials.remove(orename); } } if(!materials.containsKey(orename)) { materials.put(orename, new PhysisToolMaterial(orename, oreitem, stickorename, stickitem, output, recipe)); } } } } } } } generated = true; //Physis.logger.info("Finished tool material list"); } public static IRecipeComponentTranslator getTranslatorForMaterial(PhysisToolMaterial mat) { return RecipeHelper.getTranslatorForRecipe(mat.sourceRecipe); } public void registerRecipe(ItemStack output, Object... inputs) { IRecipeComponentTranslator translator = getTranslatorForMaterial(this); translator.registerRecipe(this.sourceRecipe, output, inputs); } @SuppressWarnings("unchecked") public static ItemStack getRecipeCompStack(Object o) { if (o instanceof ItemStack) { return (ItemStack)o; } if (o instanceof List) { return ((List<ItemStack>)o).get(0); } return null; } @SideOnly(Side.CLIENT) public static void buildTintData() { defaultTints = new int[TINTS]; for (int i=0; i<TINTS; i++) { int c = (int)((i/(double)(TINTS)) * 255); defaultTints[i] = TextureHelper.compose(c, c, c, 255); } for(Entry<String, PhysisToolMaterial> entry : materials.entrySet()) { PhysisToolMaterial mat = entry.getValue(); buildTintData(mat); } } @SideOnly(Side.CLIENT) public static void buildTintData(PhysisToolMaterial mat){ RenderSocketed.drawSocketIcon = false; if (!mat.hastint) { try { List<ItemStack> ingots = OreDictionary.getOres(mat.orename); boolean vanilla = vanillaOreNames.contains(mat.orename); boolean present = true; for (ItemStack stack : ingots) { if (stack == null || stack.getItem() == null) { continue; } if (vanilla) { if (!ModFinder.isVanilla(stack.getItem())) { // this is a vanilla ore name and the item isn't vanilla continue; } } String mod = ModFinder.idFromObject(stack.getItem()); if (excludedMods.contains(mod)) { continue; } if (!mat.intermediateTints.containsKey(stack) || mat.intermediateTints.get(stack) == null) { BufferedImage matimage = TextureHelper.getItemStackImage(stack); List<Integer> colours = TextureHelper.getImageColourRange(matimage); if (colours.isEmpty()) { mat.intermediateTints.put(stack, defaultTints.clone()); continue; } int[] mattints = processColourList(colours); mat.intermediateTints.put(stack, mattints); if( mattints == null) { present = false; } } } // if not all of the tints are there, don't bother. if(!present) { return; } // canonical colour BufferedImage matimage = TextureHelper.getItemStackImage(mat.ingot); List<Integer> colours = TextureHelper.getImageColourRange(matimage); if (colours.isEmpty()) { //no canonical tints, bail Physis.logger.warn("No valid colours for material "+mat.getMaterialName()+", using defaults"); mat.tints = defaultTints.clone(); mat.hastint = true; return; } int canonical = TextureHelper.getAverageColour(colours); double[] canonicalhsl = TextureHelper.rgb2hsl(canonical); // work out the best tint set List<TintInfo> tintinfos = new ArrayList<TintInfo>(); for (Entry<ItemStack, int[]> entry : mat.intermediateTints.entrySet()) { TintInfo info = mat.new TintInfo(entry.getValue(), canonicalhsl, entry.getKey().getDisplayName()); tintinfos.add(info); } if (tintinfos.isEmpty()) { //no tints, bail Physis.logger.warn("No valid colours for material "+mat.getMaterialName()+", using defaults"); mat.tints = defaultTints.clone(); mat.hastint = true; return; } Collections.sort(tintinfos); /*Physis.logger.info("Tint prettiness: "+mat.orename+", canonical stack: "+mat.ingot.getDisplayName()); for (TintInfo i : tintinfos) { Physis.logger.info(i.name+": "+i.prettiness); }*/ mat.tints = tintinfos.get(0).tints; BufferedImage stickimage = TextureHelper.getItemStackImage(mat.stick); mat.shafttint = TextureHelper.getAverageColour(TextureHelper.getImageColourRange(stickimage)); mat.hastint = true; } catch(Exception e) { Physis.logger.warn("Failed to generate tint data for "+mat.getMaterialName()+", will retry."); mat.hastint = false; } } RenderSocketed.drawSocketIcon = true; } private static int[] processColourList(List<Integer> colours) { int[] tint = new int[TINTS]; tint[0] = colours.remove(0); tint[TINTS-1] = colours.remove(colours.size()-1); // If this is the case... we've got some MISSING TEXTURE FUN if ((tint[0] == 0xFFF800F8 && tint[TINTS-1] == 0xFF000000)|| (tint[0] == 0xFF000000 && tint[TINTS-1] == 0xFFF800F8)) { return null; } int avetints = TINTS-2; float dper = colours.size() / (float)avetints; for(int i=0; i<avetints; i++) { int lower = Math.round(dper*i); int upper = Math.round(dper*(i+1)); int t = TextureHelper.getAverageColour(colours.subList(lower, upper)); tint[i+1] = t; } return tint; } public static boolean compareStacks(ItemStack stack1, ItemStack stack2) { if (stack1 != null || stack2 != null) { if (stack2 == null && stack1 != null || stack2 != null && stack1 == null) { return false; } if (stack1.getItem() != stack2.getItem()) { return false; } if (stack1.getItemDamage() != 32767 && stack1.getItemDamage() != stack2.getItemDamage()) { return false; } } return true; } public static PhysisToolMaterial getMaterialFromItemStack(ItemStack stack) { if (stack.stackTagCompound != null) { if(stack.stackTagCompound.hasKey(MATERIALTAG)) { String matname = stack.stackTagCompound.getString(MATERIALTAG); if (materials.containsKey(matname)) { return materials.get(matname); } } } return null; } public static void writeMaterialToStack(PhysisToolMaterial mat, ItemStack stack) { if (stack.stackTagCompound == null) { stack.stackTagCompound = new NBTTagCompound(); } stack.stackTagCompound.setString(MATERIALTAG, mat.orename); } public static PhysisToolMaterial getMaterialById(int id) { return materialsById.get(id); } public static PhysisToolMaterial getRandomMaterial(Random rand) { return materialsById.get(rand.nextInt(materialsById.size())); } private class TintInfo implements Comparable<TintInfo> { public double prettiness = 0; int[] tints; @SuppressWarnings("unused") String name; public TintInfo(int[] tints, double[] canonicalhsl, String name) { this.tints = tints; this.name = name; int dark = tints[0]; int light = tints[TINTS-1]; int median = tints[(int)Math.floor(TINTS/2.0)]; int darkbrightness = TextureHelper.getPerceptualBrightness(dark); int medbrightness = TextureHelper.getPerceptualBrightness(median); int lightbrightness = TextureHelper.getPerceptualBrightness(light); int spread = Math.abs(lightbrightness - darkbrightness); int dmdiff = Math.abs(medbrightness - darkbrightness); int lmdiff = Math.abs(lightbrightness - medbrightness); double centredness = (255 - Math.abs(dmdiff - lmdiff))/255.0; // hsl of median colour double[] medhsl = TextureHelper.rgb2hsl(median); double ldiff = Math.abs(medhsl[2] - canonicalhsl[2]); // hue difference, switching to the other way if big double hdiff = Math.abs(medhsl[0] - canonicalhsl[0]); if (hdiff > 0.5) { hdiff = 1.0 - hdiff; } // average saturation times difference in hue times half of 1-light difference //double colourdiff = (medhsl[1] + canonicalhsl[1]) * 0.5 * hdiff * (0.5 + (1-ldiff)*0.5); double colourdiff = hdiff * 2.0 * (0.5 + (1-ldiff)*0.5); this.prettiness = centredness * (0.5 +spread*0.5) * (1.0-colourdiff);//(0.5 + colourdiff*0.5); //Physis.logger.info(name+" canonical: "+Arrays.toString(canonicalhsl)+", median: "+Arrays.toString(medhsl)+", colour diff: "+ colourdiff); } @Override public int compareTo(TintInfo other) { return (int)Math.signum(other.prettiness - this.prettiness); } } }