package slimeknights.tconstruct.library.client.model;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType;
import net.minecraft.client.renderer.block.model.ItemOverride;
import net.minecraft.client.renderer.block.model.ItemOverrideList;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.world.World;
import net.minecraftforge.common.model.TRSRTransformation;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import slimeknights.mantle.client.model.BakedSimple;
import slimeknights.mantle.client.model.BakedWrapper;
import slimeknights.tconstruct.library.utils.TagUtil;
import slimeknights.tconstruct.library.utils.ToolHelper;
public class BakedToolModel extends BakedWrapper.Perspective {
protected BakedMaterialModel[] parts;
protected BakedMaterialModel[] brokenParts;
protected Map<String, IBakedModel> modifierParts;
protected final ImmutableMap<TransformType, TRSRTransformation> transforms;
protected final ImmutableList<BakedToolModelOverride> overrides;
/**
* The length of brokenParts has to match the length of parts. If a part does not have a broken texture, the entry in
* the array simply is null.
*/
public BakedToolModel(IBakedModel parent,
BakedMaterialModel[] parts,
BakedMaterialModel[] brokenParts,
Map<String, IBakedModel> modifierParts,
ImmutableMap<TransformType, TRSRTransformation> transform,
ImmutableList<BakedToolModelOverride> overrides) {
super(parent, transform);
if(parts.length != brokenParts.length) {
throw new RuntimeException("TinkerModel: Length of Parts and BrokenParts Array has to match");
}
this.parts = parts;
this.brokenParts = brokenParts;
this.modifierParts = modifierParts;
this.transforms = transform;
this.overrides = overrides;
}
@Nonnull
@Override
public ItemOverrideList getOverrides() {
return ToolItemOverrideList.INSTANCE;
}
protected static class ToolItemOverrideList extends ItemOverrideList {
private Cache<CacheKey, IBakedModel> bakedModelCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
static ToolItemOverrideList INSTANCE = new ToolItemOverrideList();
protected ToolItemOverrideList() {
super(ImmutableList.<ItemOverride>of());
}
@Nonnull
@Override
public IBakedModel handleItemState(@Nonnull IBakedModel originalModel, final ItemStack stack, @Nonnull final World world, @Nonnull final EntityLivingBase entity) {
NBTTagCompound baseTag = TagUtil.getBaseTag(stack);
IBakedModel outputModel = originalModel;
if(!baseTag.hasNoTags()) {
final BakedToolModel original = getBaseModel((BakedToolModel) originalModel, stack, world, entity);
CacheKey key = getCacheKey(stack, original, world, entity);
try {
outputModel = bakedModelCache.get(key, () -> getCompleteModel(stack, world, entity, original));
} catch(ExecutionException e) {
// do nothing, return original model
}
}
return outputModel;
}
protected CacheKey getCacheKey(ItemStack stack, BakedToolModel original, World world, EntityLivingBase entityLivingBase) {
return new CacheKey(original, stack);
}
protected IBakedModel getCompleteModel(ItemStack stack, @Nonnull World world, @Nonnull EntityLivingBase entity, BakedToolModel original) {
// get the texture for each part
ImmutableList.Builder<BakedQuad> quads = ImmutableList.builder();
addPartQuads(stack, original, quads);
addModifierQuads(stack, original, quads);
addExtraQuads(stack, original, quads, world, entity);
return new BakedSimple(quads.build(), original.transforms, original);
}
private BakedToolModel getBaseModel(@Nonnull BakedToolModel originalModel, ItemStack stack, @Nonnull World world, @Nonnull EntityLivingBase entity) {
BakedToolModel original = originalModel;
// check for an override
for(BakedToolModelOverride override : original.overrides) {
if(override.matches(stack, world, entity)) {
original = override.bakedToolModel;
}
}
return original;
}
private void addPartQuads(ItemStack stack, BakedToolModel original, ImmutableList.Builder<BakedQuad> quads) {
NBTTagList materials = TagUtil.getBaseMaterialsTagList(stack);
boolean broken = ToolHelper.isBroken(stack);
BakedMaterialModel parts[] = original.parts;
BakedMaterialModel brokenParts[] = original.brokenParts;
// the model for the part of the given material. Broken or not-broken
for(int i = 0; i < parts.length; i++) {
String id = materials.getStringTagAt(i);
IBakedModel partModel;
if(broken && brokenParts[i] != null) {
partModel = brokenParts[i].getModelByIdentifier(id);
}
else {
partModel = parts[i].getModelByIdentifier(id);
}
quads.addAll(partModel.getQuads(null, null, 0));
}
}
private void addModifierQuads(ItemStack stack, BakedToolModel original, ImmutableList.Builder<BakedQuad> quads) {
NBTTagList modifiers = TagUtil.getBaseModifiersTagList(stack);
Map<String, IBakedModel> modifierParts = original.modifierParts;
for(int i = 0; i < modifiers.tagCount(); i++) {
String modId = modifiers.getStringTagAt(i);
IBakedModel modModel = modifierParts.get(modId);
if(modModel != null) {
quads.addAll(modModel.getQuads(null, null, 0));
}
}
}
protected void addExtraQuads(ItemStack stack, BakedToolModel original, ImmutableList.Builder<BakedQuad> quads, World world, EntityLivingBase entity) {
// for custom stuff
}
}
protected static class CacheKey {
final IBakedModel parent;
final String data;
protected CacheKey(IBakedModel parent, ItemStack stack) {
this.parent = parent;
this.data = getDataFromStack(stack);
}
protected String getDataFromStack(ItemStack stack) {
return TagUtil.getTagSafe(stack).toString();
}
@Override
public boolean equals(Object o) {
if(this == o) {
return true;
}
if(o == null || getClass() != o.getClass()) {
return false;
}
CacheKey cacheKey = (CacheKey) o;
if(parent != null ? parent != cacheKey.parent : cacheKey.parent != null) {
return false;
}
return data != null ? data.equals(cacheKey.data) : cacheKey.data == null;
}
@Override
public int hashCode() {
int result = parent != null ? parent.hashCode() : 0;
result = 31 * result + (data != null ? data.hashCode() : 0);
return result;
}
}
}