/** * This class was created by <williewillus>. It's distributed as * part of the Botania Mod. Get the Source Code in github: * https://github.com/Vazkii/Botania * <p> * Botania is Open Source and distributed under the * Botania License: http://botaniamod.net/license.php */ package vazkii.botania.client.model; import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; import javax.vecmath.Matrix4f; import org.apache.commons.lang3.tuple.Pair; import com.google.common.base.Function; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.IBakedModel; import net.minecraft.client.renderer.block.model.ItemCameraTransforms; import net.minecraft.client.renderer.block.model.ItemOverrideList; import net.minecraft.client.renderer.block.model.ModelResourceLocation; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.VertexFormat; import net.minecraft.client.resources.IResourceManager; import net.minecraft.entity.EntityLivingBase; import net.minecraft.init.Blocks; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.client.model.ICustomModelLoader; import net.minecraftforge.client.model.IModel; import net.minecraftforge.client.model.IModelCustomData; import net.minecraftforge.client.model.IPerspectiveAwareModel; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.client.model.ModelLoaderRegistry; import net.minecraftforge.client.model.SimpleModelState; import net.minecraftforge.common.model.IModelState; import net.minecraftforge.common.model.TRSRTransformation; import net.minecraftforge.common.property.IExtendedBlockState; import vazkii.botania.api.BotaniaAPIClient; import vazkii.botania.api.state.BotaniaStateProps; import vazkii.botania.common.Botania; import vazkii.botania.common.block.ModBlocks; import vazkii.botania.common.item.block.ItemBlockSpecialFlower; public class SpecialFlowerModel implements IModelCustomData { // SpecialFlowerModel for when there are no blockModels registered for a subtile public static final SpecialFlowerModel INSTANCE = new SpecialFlowerModel(ImmutableMap.of(), ImmutableMap.of()); private final ImmutableMap<Optional<String>, ModelResourceLocation> blockModels; private final ImmutableMap<Optional<String>, ModelResourceLocation> itemModels; public SpecialFlowerModel(ImmutableMap<Optional<String>, ModelResourceLocation> blockModels, ImmutableMap<Optional<String>, ModelResourceLocation> itemModels) { this.blockModels = blockModels; this.itemModels = itemModels; } @Override public Collection<ResourceLocation> getDependencies() { ImmutableSet.Builder<ResourceLocation> builder = ImmutableSet.builder(); builder.addAll(blockModels.values()); builder.addAll(itemModels.values()); // Force island models to be loaded and baked. See FloatingFlowerModel. builder.addAll(BotaniaAPIClient.getRegisteredIslandTypeModels().values()); return builder.build(); } @Override public Collection<ResourceLocation> getTextures() { return ImmutableList.of(); } @Override public IBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter) { // "Bake" the SpecialFlowerModel (in reality, just create another wrapper for all the delegate blockModels) return new SpecialFlowerBakedModel(blockModels, itemModels, IPerspectiveAwareModel.MapWrapper.getTransforms(state)); } @Override public IModelState getDefaultState() { return TRSRTransformation.identity(); } @Override public IModel process(ImmutableMap<String, String> customData) { // Load the base variant from blockstate json, and also add all the model paths we received from external API ImmutableMap.Builder<Optional<String>, ModelResourceLocation> blockBuilder = ImmutableMap.builder(); ImmutableMap.Builder<Optional<String>, ModelResourceLocation> itemBuilder = ImmutableMap.builder(); for(String key : customData.keySet()) { if("base".equals(key)) { blockBuilder.put(Optional.empty(), getLocation(customData.get(key))); } } for(Map.Entry<String, ModelResourceLocation> e : BotaniaAPIClient.getRegisteredSubtileBlockModels().entrySet()) { blockBuilder.put(Optional.of(e.getKey()), e.getValue()); } for(Map.Entry<String, ModelResourceLocation> e : BotaniaAPIClient.getRegisteredSubtileItemModels().entrySet()) { itemBuilder.put(Optional.of(e.getKey()), e.getValue()); } ImmutableMap<Optional<String>, ModelResourceLocation> blockModels = blockBuilder.build(); ImmutableMap<Optional<String>, ModelResourceLocation> itemModels = itemBuilder.build(); if(blockModels.isEmpty() && itemModels.isEmpty()) return INSTANCE; return new SpecialFlowerModel(blockModels, itemModels); } private ModelResourceLocation getLocation(String json) { JsonElement e = new JsonParser().parse(json); if(e.isJsonPrimitive() && e.getAsJsonPrimitive().isString()) { return new ModelResourceLocation(e.getAsString()); } Botania.LOGGER.error("Expect ModelResourceLocation, got: ", json); return new ModelResourceLocation("builtin/missing", "missing"); } public enum Loader implements ICustomModelLoader { INSTANCE { @Override public void onResourceManagerReload(@Nonnull IResourceManager resourceManager) { } @Override public boolean accepts(ResourceLocation modelLocation) { return modelLocation.getResourceDomain().equals("botania_special") && ( modelLocation.getResourcePath().equals("specialflower") || modelLocation.getResourcePath().equals("models/block/specialflower") || modelLocation.getResourcePath().equals("models/item/specialflower")); } @Override public IModel loadModel(ResourceLocation modelLocation) throws IOException { // Load a dummy model for now, all actual blockModels added in process(). return SpecialFlowerModel.INSTANCE; } } } private static class SpecialFlowerBakedModel implements IPerspectiveAwareModel { private final ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms; private final ImmutableMap<Optional<String>, ModelResourceLocation> blockModels; private final ImmutableMap<Optional<String>, ModelResourceLocation> itemModels; private final IBakedModel baseModel; private final LoadingCache<String, Optional<IBakedModel>> bakedBlockModels; private final LoadingCache<String, Optional<IBakedModel>> bakedItemModels; SpecialFlowerBakedModel(ImmutableMap<Optional<String>, ModelResourceLocation> blockModels, ImmutableMap<Optional<String>, ModelResourceLocation> itemModels, ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> cameraTransforms) { this.blockModels = blockModels; this.itemModels = itemModels; transforms = cameraTransforms; ModelResourceLocation baseModelPath = blockModels.getOrDefault(Optional.empty(), new ModelResourceLocation("builtin/missing", "missing")); baseModel = ModelLoaderRegistry.getModelOrMissing(baseModelPath) .bake(new SimpleModelState(transforms), DefaultVertexFormats.ITEM, ModelLoader.defaultTextureGetter()); bakedBlockModels = CacheBuilder.newBuilder().build(new Loader(true)); bakedItemModels = CacheBuilder.newBuilder().build(new Loader(false)); } private class Loader extends CacheLoader<String, Optional<IBakedModel>> { private final boolean loadBlocks; Loader(boolean blocks) { loadBlocks = blocks; } @Override public Optional<IBakedModel> load(@Nonnull String key) { Map<Optional<String>, ModelResourceLocation> loadFrom = loadBlocks ? blockModels : itemModels; ModelResourceLocation loc = loadFrom.get(Optional.of(key)); if(loc == null) return Optional.empty(); IModel model = ModelLoaderRegistry.getModelOrMissing(loc); if(model == ModelLoaderRegistry.getMissingModel()) return Optional.empty(); return Optional.of(model.bake(new SimpleModelState(transforms), DefaultVertexFormats.ITEM, ModelLoader.defaultTextureGetter())); } } @Nonnull @Override public List<BakedQuad> getQuads(IBlockState state, EnumFacing face, long rand) { if(state.getBlock() != ModBlocks.specialFlower) { Botania.LOGGER.fatal("FOREIGN BLOCK: " + state); return Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelShapes().getModelManager().getMissingModel().getQuads(state, face, rand); } IExtendedBlockState extendedState = (IExtendedBlockState) state; String subtileId = extendedState.getValue(BotaniaStateProps.SUBTILE_ID); IBakedModel ret = Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelShapes().getModelManager().getMissingModel(); Optional<IBakedModel> specialModel = bakedBlockModels.getUnchecked(subtileId == null ? "" : subtileId); if(specialModel.isPresent()) ret = specialModel.get(); return ret.getQuads(state, face, rand); } @Nonnull @Override public ItemOverrideList getOverrides() { return itemHandler; } private final ItemStack roseFallback = new ItemStack(Blocks.RED_FLOWER); private final ItemOverrideList itemHandler = new ItemOverrideList(ImmutableList.of()) { @Nonnull @Override public IBakedModel handleItemState(@Nonnull IBakedModel original, ItemStack stack, World world, EntityLivingBase living) { Optional<IBakedModel> item = bakedItemModels.getUnchecked(ItemBlockSpecialFlower.getType(stack)); if(!item.isPresent()) item = bakedBlockModels.getUnchecked(ItemBlockSpecialFlower.getType(stack)); return item.isPresent() ? item.get() : Minecraft.getMinecraft().getRenderItem().getItemModelMesher().getItemModel(roseFallback); } }; @Override public boolean isAmbientOcclusion() { return baseModel.isAmbientOcclusion(); } @Override public boolean isGui3d() { return baseModel.isGui3d(); } @Override public boolean isBuiltInRenderer() { return baseModel.isBuiltInRenderer(); } @Nonnull @Override public TextureAtlasSprite getParticleTexture() { return baseModel.getParticleTexture(); } @Nonnull @Override public ItemCameraTransforms getItemCameraTransforms() { return baseModel.getItemCameraTransforms(); } @Override public Pair<? extends IBakedModel, Matrix4f> handlePerspective(ItemCameraTransforms.TransformType cameraTransformType) { return IPerspectiveAwareModel.MapWrapper.handlePerspective(this, transforms, cameraTransformType); } } }