Posted September 26, 20178 yr So I've been revisiting modding with fluids and that is going reasonably, but I wanted to try making an item fluid handler and that works fine too except I wanted to make the texture change based on whether it is full or not (like a UniversalBucket). So I looked at the ModelDynBucket and tried to follow that, but now running into a few things I have questions about. First of all, I think the concept of "baking" simply means that you take in a base model and have an opportunity to transform it (or replace it) based on custom state data. Is that a correct way to describe it? So I have a bunch of related classes, the IModel, and ICustomModelLoader, an IBakedModel, and an ItemOverrideList. But I'm getting confused about how these all work together. Starting with the ModelRegistryEvent and ModelBakeEvent. So am I supposed to handle both of these? Like register my IModel in the ModelRegistryEvent and then do something to transform/bake it during the ModelBakeEvent? Or since I have a baked model do I skip the ModelRegistryEvent and just do something in the ModelBakeEvent? Or is the ModelBakeEvent fired after all the baking is done and meant to allow processing after the baking? ... I looked at a bunch of github repositories and most mods seem to use ModelBakeEvent for doing things like replacing other mod's models and stuff and i don't see a lot of baking going on within the event handlers.... IIf I look at the bake() method it takes a parameter called IModelState but this seems to be unrelated to the item state but rather is a way to represent a bunch of transforms Also, should I be registering the IModel or an IBakedModel? And in the ModelBakeEvent is it just about registering, or do I need to call the bake() method in my IModel somehow? The bake() method takes a bunch of parameters I don't think I really have access to. So I think the bake() method is called automatically by the handleItemState(0 method of my IItemOverrideList class which also is the one that will check the state and change the texture of my item accordingly. So is handleItemState() called during the game play or is it called once (per permutation of possible custom states) during the model loading phase of starting the game? And are JSONs involved with any of this? For the UniversalBucket that I'm mostly copying there does appear to be a blockstates file for it, but it doesn't seem to have much interesting in it... So many questions!!! My most pressing question is what should I register or do in the ModelRegistryEvent and ModelBakeEvent? Here is my current code for the classes involved: package com.blogspot.jabelarminecraft.examplemod.client.models; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import javax.annotation.Nullable; import javax.vecmath.Matrix4f; import org.apache.commons.lang3.tuple.Pair; import com.blogspot.jabelarminecraft.examplemod.MainMod; import com.blogspot.jabelarminecraft.examplemod.init.ModFluids; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; 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.ItemCameraTransforms.TransformType; 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.VertexFormat; import net.minecraft.client.resources.IResourceManager; import net.minecraft.entity.EntityLivingBase; 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.ItemLayerModel; import net.minecraftforge.client.model.ItemTextureQuadConverter; import net.minecraftforge.client.model.PerspectiveMapWrapper; import net.minecraftforge.client.model.SimpleModelState; import net.minecraftforge.common.model.IModelState; import net.minecraftforge.common.model.TRSRTransformation; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidRegistry; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidUtil; public final class ModelSlimeBag implements IModel { public static final ModelResourceLocation LOCATION = new ModelResourceLocation(new ResourceLocation(MainMod.MODID, "slime_bag"), "inventory"); // minimal Z offset to prevent depth-fighting private static final float NORTH_Z_FLUID = 7.498f / 16f; private static final float SOUTH_Z_FLUID = 8.502f / 16f; public static final ModelSlimeBag MODEL = new ModelSlimeBag(); @Nullable private final ResourceLocation emptyLocation = new ResourceLocation(MainMod.MODID, "slime_bag_empty"); @Nullable private final ResourceLocation filledLocation = new ResourceLocation(MainMod.MODID, "slime_bag"); @Nullable private final Fluid fluid; public ModelSlimeBag() { this(ModFluids.SLIME); } public ModelSlimeBag(Fluid parFluid) { fluid = parFluid; } @Override public Collection<ResourceLocation> getTextures() { ImmutableSet.Builder<ResourceLocation> builder = ImmutableSet.builder(); if (emptyLocation != null) builder.add(emptyLocation); if (filledLocation != null) builder.add(filledLocation); return builder.build(); } @Override public IBakedModel bake(IModelState state, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter) { ImmutableMap<TransformType, TRSRTransformation> transformMap = PerspectiveMapWrapper.getTransforms(state); TRSRTransformation transform = state.apply(Optional.empty()).orElse(TRSRTransformation.identity()); TextureAtlasSprite fluidSprite = null; ImmutableList.Builder<BakedQuad> builder = ImmutableList.builder(); if(fluid != null) { fluidSprite = bakedTextureGetter.apply(fluid.getStill()); } if (emptyLocation != null) { // build base (insidest) IBakedModel model = (new ItemLayerModel(ImmutableList.of(emptyLocation))).bake(state, format, bakedTextureGetter); builder.addAll(model.getQuads(null, null, 0)); } if (filledLocation != null && fluidSprite != null) { TextureAtlasSprite liquid = bakedTextureGetter.apply(filledLocation); // build liquid layer (inside) builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, NORTH_Z_FLUID, EnumFacing.NORTH, fluid.getColor())); builder.addAll(ItemTextureQuadConverter.convertTexture(format, transform, liquid, fluidSprite, SOUTH_Z_FLUID, EnumFacing.SOUTH, fluid.getColor())); } return new Baked(this, builder.build(), fluidSprite, format, Maps.immutableEnumMap(transformMap), Maps.newHashMap()); } /** * Sets the liquid in the model. * fluid - Name of the fluid in the FluidRegistry * If the fluid can't be found, water is used */ @Override public ModelSlimeBag process(ImmutableMap<String, String> customData) { String fluidName = customData.get("fluid"); Fluid fluid = FluidRegistry.getFluid(fluidName); if (fluid == null) fluid = this.fluid; // create new model with correct liquid return new ModelSlimeBag(fluid); } /** * Allows to use different textures for the model. * There are 3 layers: * base - The empty bucket/container * fluid - A texture representing the liquid portion. Non-transparent = liquid * cover - An overlay that's put over the liquid (optional) * <p/> * If no liquid is given a hardcoded variant for the bucket is used. */ @Override public ModelSlimeBag retexture(ImmutableMap<String, String> textures) { // don't allow retexturing return new ModelSlimeBag(fluid); } public enum CustomModelLoader implements ICustomModelLoader { INSTANCE; @Override public boolean accepts(ResourceLocation modelLocation) { return modelLocation.getResourceDomain().equals(MainMod.MODID) && modelLocation.getResourcePath().contains("slime_bag"); } @Override public IModel loadModel(ResourceLocation modelLocation) { return MODEL; } @Override public void onResourceManagerReload(IResourceManager resourceManager) { // no need to clear cache since we create a new model instance } } private static final class BakedOverrideHandler extends ItemOverrideList { public static final BakedOverrideHandler INSTANCE = new BakedOverrideHandler(); private BakedOverrideHandler() { super(ImmutableList.of()); } @Override public IBakedModel handleItemState(IBakedModel originalModel, ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity) { FluidStack fluidStack = FluidUtil.getFluidContained(stack); // not a fluid item apparently if (fluidStack == null) { // DEBUG System.out.println("fluid stack is null, returning original model"); // empty bucket return originalModel; } // DEBUG System.out.print("Fluid stack was not null and fluid amount = "+fluidStack.amount); Baked model = (Baked)originalModel; Fluid fluid = fluidStack.getFluid(); String name = fluid.getName(); if (!model.cache.containsKey(name)) { // DEBUG System.out.println("The model cache does not have key for fluid name"); IModel parent = model.parent.process(ImmutableMap.of("fluid", name)); Function<ResourceLocation, TextureAtlasSprite> textureGetter; textureGetter = location -> Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString()); IBakedModel bakedModel = parent.bake(new SimpleModelState(model.transforms), model.format, textureGetter); model.cache.put(name, bakedModel); return bakedModel; } // DEBUG System.out.println("The model cache already has key so returning the model"); return model.cache.get(name); } } // the filled bucket is based on the empty bucket private static final class Baked implements IBakedModel { private final ModelSlimeBag parent; // FIXME: guava cache? private final Map<String, IBakedModel> cache; // contains all the baked models since they'll never change private final ImmutableMap<TransformType, TRSRTransformation> transforms; private final ImmutableList<BakedQuad> quads; private final TextureAtlasSprite particle; private final VertexFormat format; public Baked(ModelSlimeBag parent, ImmutableList<BakedQuad> quads, TextureAtlasSprite particle, VertexFormat format, ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms, Map<String, IBakedModel> cache) { this.quads = quads; this.particle = particle; this.format = format; this.parent = parent; this.transforms = transforms; this.cache = cache; } @Override public ItemOverrideList getOverrides() { return BakedOverrideHandler.INSTANCE; } @Override public Pair<? extends IBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType) { return PerspectiveMapWrapper.handlePerspective(this, transforms, cameraTransformType); } @Override public List<BakedQuad> getQuads(@Nullable IBlockState state, @Nullable EnumFacing side, long rand) { if(side == null) return quads; return ImmutableList.of(); } @Override public boolean isAmbientOcclusion() { return true; } @Override public boolean isGui3d() { return false; } @Override public boolean isBuiltInRenderer() { return false; } @Override public TextureAtlasSprite getParticleTexture() { return particle; } } } Check out my tutorials here: http://jabelarminecraft.blogspot.com/
September 26, 20178 yr Author Thanks so much for the detailed explanation. I've also been tracing the execution last night and some of this is becoming a lot clearer now, especially the order of the operations. I'm sure I'll have some follow on questions as I work through it a bit more... Check out my tutorials here: http://jabelarminecraft.blogspot.com/
September 26, 20178 yr Author Okay, got my custom item model working! Do have a question though. You said that "ModelBakeEvent" is virtually never needed. However, in code that I've seen (and in my case copied) that event is still handled to setCustomMeshDefinition() for the loader and registerItemVariants() to the bakery. I tried not doing that and it then doesn't find the model. Are those two methods supposed to be done in the ModelBakeEvent, or are they supposed to be done in the ModelRegistryEvent? Or does it matter? Check out my tutorials here: http://jabelarminecraft.blogspot.com/
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.