package slimeknights.tconstruct.library.materials;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.text.translation.I18n;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import slimeknights.mantle.util.RecipeMatch;
import slimeknights.mantle.util.RecipeMatchRegistry;
import slimeknights.tconstruct.common.config.Config;
import slimeknights.tconstruct.library.TinkerRegistry;
import slimeknights.tconstruct.library.Util;
import slimeknights.tconstruct.library.client.CustomFontColor;
import slimeknights.tconstruct.library.client.MaterialRenderInfo;
import slimeknights.tconstruct.library.traits.ITrait;
public class Material extends RecipeMatchRegistry {
public static final Material UNKNOWN = new Material("unknown", TextFormatting.WHITE);
public static final String LOC_Name = "material.%s.name";
public static final String LOC_Prefix = "material.%s.prefix";
// How much the different items are "worth"
// the values are used for both liquid conversion as well as part crafting
public static final int VALUE_Ingot = 144;
public static final int VALUE_Nugget = VALUE_Ingot / 9;
public static final int VALUE_Fragment = VALUE_Ingot / 4;
public static final int VALUE_Shard = VALUE_Ingot / 2;
public static final int VALUE_Gem = 666; // divisible by 3!
public static final int VALUE_Block = VALUE_Ingot * 9;
public static final int VALUE_SearedBlock = VALUE_Ingot * 2;
public static final int VALUE_SearedMaterial = VALUE_Ingot / 2;
public static final int VALUE_Glass = 1000;
public static final int VALUE_BrickBlock = VALUE_Ingot * 4;
public static final int VALUE_SlimeBall = 250;
public static int VALUE_Ore() {
return (int) (VALUE_Ingot * Config.oreToIngotRatio);
}
static {
UNKNOWN.addStats(new HeadMaterialStats(1, 1, 1, 0));
UNKNOWN.addStats(new HandleMaterialStats(1f, 0));
UNKNOWN.addStats(new ExtraMaterialStats(0));
UNKNOWN.addStats(new BowMaterialStats(1f, 1f, 0f));
UNKNOWN.addStats(new BowStringMaterialStats(1f));
UNKNOWN.addStats(new ArrowShaftMaterialStats(1f, 0));
UNKNOWN.addStats(new FletchingMaterialStats(1f, 1f));
UNKNOWN.addStats(new ProjectileMaterialStats());
}
/**
* This String uniquely identifies a material.
*/
public final String identifier;
/** The fluid associated with this material, can be null */
protected Fluid fluid;
/** Material can be crafted into parts in the PartBuilder */
protected boolean craftable;
/** Material can be cast into parts using the Smeltery and a Cast. Fluid must be NON NULL */
protected boolean castable;
/**
* Client-Information
* How the material will be rendered on tinker tools etc.
*/
@SideOnly(Side.CLIENT)
public MaterialRenderInfo renderInfo;// = new MaterialRenderInfo.Default(0xffffff);
public int materialTextColor = 0xffffff; // used in tooltips and other text. Saved in NBT.
/**
* This item, if it is not null, represents the material for rendering.
* In general if you want to give a person this material, you can give them this item.
*/
private ItemStack representativeItem;
/**
* This item will be used instead of the generic shard item when returning leftovers.
*/
private ItemStack shardItem;
// we use a specific map for 2 reasons:
// * A Map so we can obtain the stats we want quickly
// * the linked map to ensure the order when iterating
protected final Map<String, IMaterialStats> stats = new LinkedHashMap<String, IMaterialStats>();
/** Stat-ID -> Traits */
protected final Map<String, List<ITrait>> traits = new LinkedHashMap<String, List<ITrait>>();
public Material(String identifier, TextFormatting textColor) {
this(identifier, Util.enumChatFormattingToColor(textColor));
}
public Material(String identifier, int color) {
this.identifier = Util.sanitizeLocalizationString(identifier); // lowercases and removes whitespaces
// if invisible, make it fully opaque.
if(((color >> 24) & 0xFF) == 0) {
color |= 0xFF << 24;
}
this.materialTextColor = color;
if(FMLCommonHandler.instance().getSide().isClient()) {
setRenderInfo(color);
}
}
/*** If false the material will not be displayed to the user anywhere. Used for special or internal materials. */
public boolean isHidden() {
return false;
}
/** Associates this fluid with the material. Used for melting/casting items. */
public Material setFluid(Fluid fluid) {
if(fluid != null && !FluidRegistry.isFluidRegistered(fluid)) {
TinkerRegistry.log.warn("Materials cannot have an unregistered fluid associated with them!");
}
this.fluid = fluid;
return this;
}
/** Setting this to true allows to craft parts in the PartBuilder */
public Material setCraftable(boolean craftable) {
this.craftable = craftable;
return this;
}
public boolean isCraftable() {
return this.craftable || (Config.craftCastableMaterials && castable);
}
/** Setting this to true allows to cast parts of this material. NEEDS TO HAVE A FLUID SET BEFOREHAND! */
public Material setCastable(boolean castable) {
this.castable = castable;
return this;
}
public boolean isCastable() {
return hasFluid() && this.castable;
}
/**
* The display information for the Material. You should totally set this if you want your material to be visible.
*
* @param renderInfo How the textures for the material are generated
*/
@SideOnly(Side.CLIENT)
public void setRenderInfo(MaterialRenderInfo renderInfo) {
this.renderInfo = renderInfo;
}
@SideOnly(Side.CLIENT)
public MaterialRenderInfo setRenderInfo(int color) {
setRenderInfo(new MaterialRenderInfo.Default(color));
return renderInfo;
}
/* Stats */
/**
* Do not use this function directly stats. Use TinkerRegistry.addMaterialStats instead.
*/
public Material addStats(IMaterialStats materialStats) {
this.stats.put(materialStats.getIdentifier(), materialStats);
return this;
}
/**
* Returns the given type of stats if the material has them. Returns null Otherwise.
*/
private IMaterialStats getStatsSafe(String identifier) {
if(identifier == null || identifier.isEmpty()) {
return null;
}
for(IMaterialStats stat : stats.values()) {
if(identifier.equals(stat.getIdentifier())) {
return stat;
}
}
return null;
}
/**
* Returns the material stats of the given type of this material.
*
* @param identifier Identifier of the material.
* @param <T> Type of the Stats are determined by return value. Use the correct
* @return The stats found or null if none present.
*/
@SuppressWarnings("unchecked")
public <T extends IMaterialStats> T getStats(String identifier) {
return (T) getStatsSafe(identifier);
}
@SuppressWarnings("unchecked")
public <T extends IMaterialStats> T getStatsOrUnknown(String identifier) {
T stats = (T) getStatsSafe(identifier);
if(stats == null && this != UNKNOWN) {
return UNKNOWN.getStats(identifier);
}
return stats;
}
public Collection<IMaterialStats> getAllStats() {
return stats.values();
}
public boolean hasStats(String identifier) {
return getStats(identifier) != null;
}
/* Traits */
/**
* Adds the trait as the default trait, will be used if no more specific one is present time.
*/
public Material addTrait(ITrait materialTrait) {
return addTrait(materialTrait, null);
}
/**
* Adds the trait to be added if the specified stats are used.
*/
public Material addTrait(ITrait materialTrait, String dependency) {
// register unregistered traits
if(TinkerRegistry.checkMaterialTrait(this, materialTrait, dependency)) {
getStatTraits(dependency).add(materialTrait);
}
return this;
}
/** Obtains the list of traits for the given stat, creates it if it doesn't exist yet. */
protected List<ITrait> getStatTraits(String id) {
if(!this.traits.containsKey(id)) {
this.traits.put(id, new LinkedList<ITrait>());
}
return this.traits.get(id);
}
/**
* Returns whether the material has a trait with that identifier.
*/
public boolean hasTrait(String identifier, String stats) {
if(identifier == null || identifier.isEmpty()) {
return false;
}
for(ITrait trait : getStatTraits(stats)) {
if(trait.getIdentifier().equals(identifier)) {
return true;
}
}
return false;
}
public List<ITrait> getDefaultTraits() {
return ImmutableList.copyOf(getStatTraits(null));
}
public List<ITrait> getAllTraitsForStats(String stats) {
if(this.traits.containsKey(stats)) {
return ImmutableList.copyOf(this.traits.get(stats));
}
else if(this.traits.containsKey(null)) {
return ImmutableList.copyOf(this.traits.get(null));
}
return ImmutableList.of();
}
public Collection<ITrait> getAllTraits() {
ImmutableSet.Builder<ITrait> builder = ImmutableSet.builder();
for(List<ITrait> traitlist : traits.values()) {
builder.addAll(traitlist);
}
return builder.build();
}
/* Data about the material itself */
public boolean hasFluid() {
return fluid != null;
}
public Fluid getFluid() {
return fluid;
}
public void addItemIngot(String oredict) {
this.addItem(oredict, 1, Material.VALUE_Ingot);
}
public void addCommonItems(String oredictSuffix) {
this.addItem("ingot" + oredictSuffix, 1, Material.VALUE_Ingot);
this.addItem("nugget" + oredictSuffix, 1, Material.VALUE_Nugget);
this.addItem("block" + oredictSuffix, 1, Material.VALUE_Block);
}
public void setRepresentativeItem(Item representativeItem) {
setRepresentativeItem(new ItemStack(representativeItem));
}
public void setRepresentativeItem(Block representativeBlock) {
setRepresentativeItem(new ItemStack(representativeBlock));
}
public void setRepresentativeItem(ItemStack representativeItem) {
if(representativeItem == null) {
this.representativeItem = null;
}
else if(matches(representativeItem) != null) {
this.representativeItem = representativeItem;
}
else {
TinkerRegistry.log.warn("Itemstack {} cannot represent material {} since it is not associated with the material!",
representativeItem.toString(),
identifier);
}
}
public ItemStack getRepresentativeItem() {
return representativeItem;
}
public void setShard(Item item) {
setShard(new ItemStack(item));
}
public void setShard(ItemStack stack) {
if(stack == null) {
this.shardItem = null;
}
else {
RecipeMatch.Match match = matches(stack);
if(match != null) {
if(match.amount == VALUE_Shard) {
this.shardItem = stack;
}
else {
TinkerRegistry.log.warn("Itemstack {} cannot be shard of material {} since it does not have the correct value! (is {}, has to be {})",
representativeItem.toString(),
identifier,
match.amount,
VALUE_Shard);
}
}
else {
TinkerRegistry.log.warn("Itemstack {} cannot be shard of material {} since it is not associated with the material!",
stack.toString(),
identifier);
}
}
}
public ItemStack getShard() {
if(shardItem != null) {
return shardItem.copy();
}
return null;
}
public boolean hasItems() {
return !items.isEmpty();
}
public String getLocalizedName() {
return Util.translate(LOC_Name, getIdentifier());
}
/** Takes a string and turns it into a named variant for this material. E.g. pickaxe -> wooden pickaxe */
public String getLocalizedItemName(String itemName) {
if(this == UNKNOWN) {
return itemName;
}
if(I18n.canTranslate(String.format(LOC_Prefix, getIdentifier()))) {
return I18n.translateToLocalFormatted(String.format(LOC_Prefix, Util
.sanitizeLocalizationString(identifier)), itemName);
}
return getLocalizedName() + " " + itemName;
}
public String getLocalizedNameColored() {
return getTextColor() + getLocalizedName();
}
public String getIdentifier() {
return identifier;
}
public String getTextColor() {
return CustomFontColor.encodeColor(materialTextColor);
}
public static String getCombinedItemName(String itemName, Collection<Material> materials) {
// no material
if(materials.isEmpty() || materials.stream().allMatch(Material.UNKNOWN::equals)) {
return itemName;
}
// only one material - prefix
if(materials.size() == 1) {
return materials.iterator().next().getLocalizedItemName(itemName);
}
// multiple materials. we'll have to combine
StringBuilder sb = new StringBuilder();
Iterator<Material> iter = materials.iterator();
Material material = iter.next();
sb.append(material.getLocalizedName());
while(iter.hasNext()) {
material = iter.next();
sb.append("-");
sb.append(material.getLocalizedName());
}
sb.append(" ");
sb.append(itemName);
return sb.toString();
}
}