From 4f6784b7333ca3414d71ecf39faf1452326d3694 Mon Sep 17 00:00:00 2001 From: Bernhard Bonigl Date: Tue, 15 Dec 2015 22:19:04 +0100 Subject: [PATCH] Add a dynamic bucket model that displays the animated liquid contained Has a config option (default off) that replaces the vanilla buckets with the forge bucket model New original bucket textures from mr_hazard --- .../client/gui/ForgeGuiFactory.java | 25 ++ .../model/ItemTextureQuadConverter.java | 273 +++++++++++++++++ .../client/model/ModelDynBucket.java | 274 ++++++++++++++++++ .../client/model/ModelLoader.java | 70 +++++ .../client/model/ModelLoaderRegistry.java | 1 + .../common/ForgeModContainer.java | 12 + .../common/config/Configuration.java | 1 + .../assets/forge/blockstates/dynbucket.json | 18 ++ .../resources/assets/forge/lang/en_US.lang | 3 + .../assets/forge/models/item/bucket.json | 18 ++ .../assets/forge/models/item/bucket_milk.json | 20 ++ .../forge/textures/items/bucket_base.png | Bin 0 -> 597 bytes .../forge/textures/items/bucket_cover.png | Bin 0 -> 611 bytes .../forge/textures/items/bucket_fluid.png | Bin 0 -> 157 bytes .../minecraftforge/debug/DynBucketTest.java | 179 ++++++++++++ .../minecraftforge/debug/ModelFluidDebug.java | 34 +++ .../assets/forge/blockstates/dynbottle.json | 18 ++ .../forge/textures/blocks/milk_flow.png | Bin 0 -> 5163 bytes .../textures/blocks/milk_flow.png.mcmeta | 3 + .../forge/textures/blocks/milk_still.png | Bin 0 -> 6438 bytes .../textures/blocks/milk_still.png.mcmeta | 5 + .../blockstates/TestFluidBlock.json | 4 + 22 files changed, 958 insertions(+) create mode 100644 src/main/java/net/minecraftforge/client/model/ItemTextureQuadConverter.java create mode 100644 src/main/java/net/minecraftforge/client/model/ModelDynBucket.java create mode 100644 src/main/resources/assets/forge/blockstates/dynbucket.json create mode 100644 src/main/resources/assets/forge/models/item/bucket.json create mode 100644 src/main/resources/assets/forge/models/item/bucket_milk.json create mode 100644 src/main/resources/assets/forge/textures/items/bucket_base.png create mode 100644 src/main/resources/assets/forge/textures/items/bucket_cover.png create mode 100644 src/main/resources/assets/forge/textures/items/bucket_fluid.png create mode 100644 src/test/java/net/minecraftforge/debug/DynBucketTest.java create mode 100644 src/test/resources/assets/forge/blockstates/dynbottle.json create mode 100644 src/test/resources/assets/forge/textures/blocks/milk_flow.png create mode 100644 src/test/resources/assets/forge/textures/blocks/milk_flow.png.mcmeta create mode 100644 src/test/resources/assets/forge/textures/blocks/milk_still.png create mode 100644 src/test/resources/assets/forge/textures/blocks/milk_still.png.mcmeta diff --git a/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java b/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java index fcfee4e9e..bb11f3029 100644 --- a/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java +++ b/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java @@ -104,6 +104,7 @@ public class ForgeGuiFactory implements IModGuiFactory { List list = new ArrayList(); list.add(new DummyCategoryElement("forgeCfg", "forge.configgui.ctgy.forgeGeneralConfig", GeneralEntry.class)); + list.add(new DummyCategoryElement("forgeClientCfg", "forge.configgui.ctgy.forgeClientConfig", ClientEntry.class)); list.add(new DummyCategoryElement("forgeChunkLoadingCfg", "forge.configgui.ctgy.forgeChunkLoadingConfig", ChunkLoaderEntry.class)); list.add(new DummyCategoryElement("forgeVersionCheckCfg", "forge.configgui.ctgy.VersionCheckConfig", VersionCheckEntry.class)); return list; @@ -133,6 +134,30 @@ public class ForgeGuiFactory implements IModGuiFactory } } + /** + * This custom list entry provides the Client only Settings entry on the Minecraft Forge Configuration screen. + * It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen. + */ + public static class ClientEntry extends CategoryEntry + { + public ClientEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop) + { + super(owningScreen, owningEntryList, prop); + } + + @Override + protected GuiScreen buildChildScreen() + { + // This GuiConfig object specifies the configID of the object and as such will force-save when it is closed. The parent + // GuiConfig object's entryList will also be refreshed to reflect the changes. + return new GuiConfig(this.owningScreen, + (new ConfigElement(ForgeModContainer.getConfig().getCategory(Configuration.CATEGORY_CLIENT))).getChildElements(), + this.owningScreen.modID, Configuration.CATEGORY_CLIENT, this.configElement.requiresWorldRestart() || this.owningScreen.allRequireWorldRestart, + this.configElement.requiresMcRestart() || this.owningScreen.allRequireMcRestart, + GuiConfig.getAbridgedConfigPath(ForgeModContainer.getConfig().toString())); + } + } + /** * This custom list entry provides the Forge Chunk Manager Config entry on the Minecraft Forge Configuration screen. * It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen. diff --git a/src/main/java/net/minecraftforge/client/model/ItemTextureQuadConverter.java b/src/main/java/net/minecraftforge/client/model/ItemTextureQuadConverter.java new file mode 100644 index 000000000..074e687e5 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/ItemTextureQuadConverter.java @@ -0,0 +1,273 @@ +package net.minecraftforge.client.model; + +import com.google.common.collect.Lists; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.util.EnumFacing; +import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad; + +import javax.vecmath.Vector4f; +import java.util.List; + +public final class ItemTextureQuadConverter +{ + private ItemTextureQuadConverter() + { + // non-instantiable + } + + /** + * Takes a texture and converts it into BakedQuads. + * The conversion is done by scanning the texture horizontally and vertically and creating "strips" of the texture. + * Strips that are of the same size and follow each other are converted into one bigger quad. + *
+ * The resulting list of quads is the texture represented as a list of horizontal OR vertical quads, + * depending on which creates less quads. If the amount of quads is equal, horizontal is preferred. + * + * @param format + * @param template The input texture to convert + * @param sprite The texture whose UVs shall be used @return The generated quads. + */ + public static List convertTexture(VertexFormat format, TRSRTransformation transform, TextureAtlasSprite template, TextureAtlasSprite sprite, float z, EnumFacing facing, int color) + { + List horizontal = convertTextureHorizontal(format, transform, template, sprite, z, facing, color); + List vertical = convertTextureVertical(format, transform, template, sprite, z, facing, color); + + return horizontal.size() >= vertical.size() ? horizontal : vertical; + } + + /** + * Scans a texture and converts it into a list of horizontal strips stacked on top of each other. + * The height of the strips is as big as possible. + */ + public static List convertTextureHorizontal(VertexFormat format, TRSRTransformation transform, TextureAtlasSprite template, TextureAtlasSprite sprite, float z, EnumFacing facing, int color) + { + int w = template.getIconWidth(); + int h = template.getIconHeight(); + int[] data = template.getFrameTextureData(0)[0]; + List quads = Lists.newArrayList(); + + // the upper left x-position of the current quad + int start = -1; + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + // current pixel + int pixel = data[y * w + x]; + + // no current quad but found a new one + if (start < 0 && isVisible(pixel)) + { + start = x; + } + // got a current quad, but it ends here + if (start >= 0 && !isVisible(pixel)) + { + // we now check if the visibility of the next row matches the one fo the current row + // if they are, we can extend the quad downwards + int endY = y + 1; + boolean sameRow = true; + while (sameRow) + { + for (int i = 0; i < w; i++) + { + int px1 = data[y * w + i]; + int px2 = data[endY * w + i]; + if (isVisible(px1) != isVisible(px2)) + { + sameRow = false; + break; + } + } + if (sameRow) + { + endY++; + } + } + + // create the quad + quads.add(genQuad(format, transform, start, y, x, endY, z, sprite, facing, color)); + + // update Y if all the rows match. no need to rescan + if (endY - y > 1) + { + y = endY - 1; + } + // clear current quad + start = -1; + } + } + } + + return quads; + } + + /** + * Scans a texture and converts it into a list of vertical strips stacked next to each other from left to right. + * The width of the strips is as big as possible. + */ + public static List convertTextureVertical(VertexFormat format, TRSRTransformation transform, TextureAtlasSprite template, TextureAtlasSprite sprite, float z, EnumFacing facing, int color) + { + int w = template.getIconWidth(); + int h = template.getIconHeight(); + int[] data = template.getFrameTextureData(0)[0]; + List quads = Lists.newArrayList(); + + // the upper left y-position of the current quad + int start = -1; + for (int x = 0; x < w; x++) + { + for (int y = 0; y < h; y++) + { + // current pixel + int pixel = data[y * w + x]; + + // no current quad but found a new one + if (start < 0 && isVisible(pixel)) + { + start = y; + } + // got a current quad, but it ends here + if (start >= 0 && !isVisible(pixel)) + { + // we now check if the visibility of the next column matches the one fo the current row + // if they are, we can extend the quad downwards + int endX = x + 1; + boolean sameColumn = true; + while (sameColumn) + { + for (int i = 0; i < h; i++) + { + int px1 = data[i * w + x]; + int px2 = data[i * w + endX]; + if (isVisible(px1) != isVisible(px2)) + { + sameColumn = false; + break; + } + } + if (sameColumn) + { + endX++; + } + } + + // create the quad + quads.add(genQuad(format, transform, x, start, endX, y, z, sprite, facing, color)); + + // update X if all the columns match. no need to rescan + if (endX - x > 1) + { + x = endX - 1; + } + // clear current quad + start = -1; + } + } + } + + return quads; + } + + // true if alpha != 0 + private static boolean isVisible(int color) + { + return (color >> 24 & 255) > 0; + } + + /** + * Generates a Front/Back quad for an itemmodel. Therefore only supports facing NORTH and SOUTH. + */ + public static UnpackedBakedQuad genQuad(VertexFormat format, TRSRTransformation transform, float x1, float y1, float x2, float y2, float z, TextureAtlasSprite sprite, EnumFacing facing, int color) + { + float u1 = sprite.getInterpolatedU(x1); + float v1 = sprite.getInterpolatedV(y1); + float u2 = sprite.getInterpolatedU(x2); + float v2 = sprite.getInterpolatedV(y2); + + x1 /= 16f; + y1 /= 16f; + x2 /= 16f; + y2 /= 16f; + + float tmp = y1; + y1 = 1f - y2; + y2 = 1f - tmp; + + return putQuad(format, transform, facing, color, x1, y1, x2, y2, z, u1, v1, u2, v2); + } + + private static UnpackedBakedQuad putQuad(VertexFormat format, TRSRTransformation transform, EnumFacing side, int color, + float x1, float y1, float x2, float y2, float z, + float u1, float v1, float u2, float v2) + { + side = side.getOpposite(); + UnpackedBakedQuad.Builder builder = new UnpackedBakedQuad.Builder(format); + builder.setQuadTint(-1); + builder.setQuadOrientation(side); + builder.setQuadColored(); + + if (side == EnumFacing.NORTH) + { + putVertex(builder, format, transform, side, x1, y1, z, u1, v2, color); + putVertex(builder, format, transform, side, x2, y1, z, u2, v2, color); + putVertex(builder, format, transform, side, x2, y2, z, u2, v1, color); + putVertex(builder, format, transform, side, x1, y2, z, u1, v1, color); + } else + { + putVertex(builder, format, transform, side, x1, y1, z, u1, v2, color); + putVertex(builder, format, transform, side, x1, y2, z, u1, v1, color); + putVertex(builder, format, transform, side, x2, y2, z, u2, v1, color); + putVertex(builder, format, transform, side, x2, y1, z, u2, v2, color); + } + return builder.build(); + } + + private static void putVertex(UnpackedBakedQuad.Builder builder, VertexFormat format, TRSRTransformation transform, EnumFacing side, + float x, float y, float z, float u, float v, int color) + { + Vector4f vec = new Vector4f(); + for (int e = 0; e < format.getElementCount(); e++) + { + switch (format.getElement(e).getUsage()) + { + case POSITION: + if (transform == TRSRTransformation.identity()) + { + builder.put(e, x, y, z, 1); + } + // only apply the transform if it's not identity + else + { + vec.x = x; + vec.y = y; + vec.z = z; + vec.w = 1; + transform.getMatrix().transform(vec); + builder.put(e, vec.x, vec.y, vec.z, vec.w); + } + break; + case COLOR: + float r = ((color >> 16) & 0xFF) / 255f; // red + float g = ((color >> 8) & 0xFF) / 255f; // green + float b = ((color >> 0) & 0xFF) / 255f; // blue + float a = ((color >> 24) & 0xFF) / 255f; // alpha + builder.put(e, r, g, b, a); + break; + case UV: + if (format.getElement(e).getIndex() == 0) + { + builder.put(e, u, v, 0f, 1f); + break; + } + case NORMAL: + builder.put(e, (float) side.getFrontOffsetX(), (float) side.getFrontOffsetY(), (float) side.getFrontOffsetZ(), 0f); + break; + default: + builder.put(e); + break; + } + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/ModelDynBucket.java b/src/main/java/net/minecraftforge/client/model/ModelDynBucket.java new file mode 100644 index 000000000..0e39043e4 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/ModelDynBucket.java @@ -0,0 +1,274 @@ +package net.minecraftforge.client.model; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +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.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.model.IBakedModel; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fluids.*; +import org.apache.commons.lang3.tuple.Pair; + +import javax.vecmath.Matrix4f; +import javax.vecmath.Quat4f; +import java.io.IOException; +import java.util.Collection; +import java.util.Map; + +public class ModelDynBucket implements IModel, IModelCustomData, IRetexturableModel +{ + public static final ModelResourceLocation LOCATION = new ModelResourceLocation(new ResourceLocation("forge", "dynbucket"), "inventory"); + + // minimal Z offset to prevent depth-fighting + private static final float NORTH_Z_BASE = 7.496f / 16f; + private static final float SOUTH_Z_BASE = 8.504f / 16f; + private static final float NORTH_Z_FLUID = 7.498f / 16f; + private static final float SOUTH_Z_FLUID = 8.502f / 16f; + + public static final IModel MODEL = new ModelDynBucket(); + + protected final ResourceLocation baseLocation; + protected final ResourceLocation liquidLocation; + protected final ResourceLocation coverLocation; + + protected final Fluid fluid; + protected final boolean flipGas; + + public ModelDynBucket() + { + this(null, null, null, FluidRegistry.WATER, false); + } + + public ModelDynBucket(ResourceLocation baseLocation, ResourceLocation liquidLocation, ResourceLocation coverLocation, Fluid fluid, boolean flipGas) + { + this.baseLocation = baseLocation; + this.liquidLocation = liquidLocation; + this.coverLocation = coverLocation; + this.fluid = fluid; + this.flipGas = flipGas; + } + + @Override + public Collection getDependencies() + { + return ImmutableList.of(); + } + + @Override + public Collection getTextures() + { + ImmutableSet.Builder builder = ImmutableSet.builder(); + if (baseLocation != null) + builder.add(baseLocation); + if (liquidLocation != null) + builder.add(liquidLocation); + if (coverLocation != null) + builder.add(coverLocation); + + return builder.build(); + } + + @Override + public IFlexibleBakedModel bake(IModelState state, VertexFormat format, + Function bakedTextureGetter) + { + + ImmutableMap transformMap = IPerspectiveAwareModel.MapWrapper.getTransforms(state); + + // if the fluid is a gas wi manipulate the initial state to be rotated 180° to turn it upside down + if (flipGas && fluid.isGaseous()) + { + state = new ModelStateComposition(state, TRSRTransformation.blockCenterToCorner(new TRSRTransformation(null, new Quat4f(0, 0, 1, 0), null, null))); + } + + TRSRTransformation transform = state.apply(Optional.absent()).or(TRSRTransformation.identity()); + TextureAtlasSprite fluidSprite = bakedTextureGetter.apply(fluid.getStill()); + ImmutableList.Builder builder = ImmutableList.builder(); + + if (baseLocation != null) + { + // build base (insidest) + IFlexibleBakedModel model = (new ItemLayerModel(ImmutableList.of(baseLocation))).bake(state, format, bakedTextureGetter); + builder.addAll(model.getGeneralQuads()); + } + if (liquidLocation != null) + { + TextureAtlasSprite liquid = bakedTextureGetter.apply(liquidLocation); + // 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())); + } + if (coverLocation != null) + { + // cover (the actual item around the other two) + TextureAtlasSprite base = bakedTextureGetter.apply(coverLocation); + builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, NORTH_Z_BASE, base, EnumFacing.NORTH, 0xffffffff)); + builder.add(ItemTextureQuadConverter.genQuad(format, transform, 0, 0, 16, 16, SOUTH_Z_BASE, base, EnumFacing.SOUTH, 0xffffffff)); + } + + + return new BakedDynBucket(this, builder.build(), fluidSprite, format, Maps.immutableEnumMap(transformMap), Maps.newHashMap()); + } + + @Override + public IModelState getDefaultState() + { + return TRSRTransformation.identity(); + } + + /** + * Sets the liquid in the model. + * fluid - Name of the fluid in the FluidRegistry + * flipGas - If "true" the model will be flipped upside down if the liquid is a gas. If "false" it wont + *

+ * If the fluid can't be found, water is used + */ + @Override + public IModel process(ImmutableMap customData) + { + String fluidName = customData.get("fluid"); + Fluid fluid = FluidRegistry.getFluid(fluidName); + + if (fluid == null) fluid = this.fluid; + + boolean flip = flipGas; + if (customData.containsKey("flipGas")) + { + String flipStr = customData.get("flipGas"); + if (flipStr.equals("true")) flip = true; + else if (flipStr.equals("false")) flip = false; + else + throw new IllegalArgumentException(String.format("DynBucket custom data \"flipGas\" must have value \'true\' or \'false\' (was \'%s\')", flipStr)); + } + + // create new model with correct liquid + return new ModelDynBucket(baseLocation, liquidLocation, coverLocation, fluid, flip); + } + + /** + * 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) + *

+ * If no liquid is given a hardcoded variant for the bucket is used. + */ + @Override + public IModel retexture(ImmutableMap textures) + { + + ResourceLocation base = baseLocation; + ResourceLocation liquid = liquidLocation; + ResourceLocation cover = coverLocation; + + if (textures.containsKey("base")) + base = new ResourceLocation(textures.get("base")); + if (textures.containsKey("fluid")) + liquid = new ResourceLocation(textures.get("fluid")); + if (textures.containsKey("cover")) + cover = new ResourceLocation(textures.get("cover")); + + return new ModelDynBucket(base, liquid, cover, fluid, flipGas); + } + + public enum LoaderDynBucket implements ICustomModelLoader + { + instance; + + @Override + public boolean accepts(ResourceLocation modelLocation) + { + return modelLocation.getResourceDomain().equals("forge") && modelLocation.getResourcePath().contains("forgebucket"); + } + + @Override + public IModel loadModel(ResourceLocation modelLocation) throws IOException + { + return MODEL; + } + + @Override + public void onResourceManagerReload(IResourceManager resourceManager) + { + // no need to clear cache since we create a new model instance + } + } + + // the dynamic bucket is based on the empty bucket + protected static class BakedDynBucket extends ItemLayerModel.BakedModel implements ISmartItemModel, IPerspectiveAwareModel + { + + private final ModelDynBucket parent; + private final Map cache; // contains all the baked models since they'll never change + private final ImmutableMap transforms; + + public BakedDynBucket(ModelDynBucket parent, + ImmutableList quads, TextureAtlasSprite particle, VertexFormat format, ImmutableMap transforms, + Map cache) + { + super(quads, particle, format); + this.parent = parent; + this.transforms = transforms; + this.cache = cache; + } + + @Override + public IBakedModel handleItemState(ItemStack stack) + { + FluidStack fluidStack = FluidContainerRegistry.getFluidForFilledItem(stack); + if (fluidStack == null) + { + if (stack.getItem() instanceof IFluidContainerItem) + { + fluidStack = ((IFluidContainerItem) stack.getItem()).getFluid(stack); + } + } + + // not a fluid item apparently + if (fluidStack == null) + return this; + + + Fluid fluid = fluidStack.getFluid(); + String name = fluid.getName(); + + if (!cache.containsKey(name)) + { + IModel model = parent.process(ImmutableMap.of("fluid", name)); + Function textureGetter; + textureGetter = new Function() + { + public TextureAtlasSprite apply(ResourceLocation location) + { + return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString()); + } + }; + + IFlexibleBakedModel bakedModel = model.bake(new SimpleModelState(transforms), this.getFormat(), textureGetter); + cache.put(name, bakedModel); + return bakedModel; + } + + return cache.get(name); + } + + @Override + public Pair handlePerspective(TransformType cameraTransformType) + { + return IPerspectiveAwareModel.MapWrapper.handlePerspective(this, transforms, cameraTransformType); + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/ModelLoader.java b/src/main/java/net/minecraftforge/client/model/ModelLoader.java index f0b4850b9..050bbbf3e 100644 --- a/src/main/java/net/minecraftforge/client/model/ModelLoader.java +++ b/src/main/java/net/minecraftforge/client/model/ModelLoader.java @@ -45,10 +45,17 @@ import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.client.resources.model.ModelRotation; import net.minecraft.client.resources.model.SimpleBakedModel; import net.minecraft.client.resources.model.WeightedBakedModel; +import net.minecraft.init.Items; import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; import net.minecraft.util.IRegistry; import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.ForgeModContainer; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidContainerRegistry; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fml.common.FMLLog; import net.minecraftforge.fml.common.registry.GameData; import net.minecraftforge.fml.common.registry.RegistryDelegate; @@ -177,6 +184,7 @@ public class ModelLoader extends ModelBakery registerVariantNames(); for(Item item : GameData.getItemRegistry().typeSafeIterable()) { + // default loading for(String s : (List)getVariantNames(item)) { ResourceLocation file = getItemLocation(s); @@ -201,6 +209,68 @@ public class ModelLoader extends ModelBakery } } } + + // replace vanilla bucket models if desired. done afterwards for performance reasons + if(ForgeModContainer.replaceVanillaBucketModel) + { + // empty bucket + for(String s : getVariantNames(Items.bucket)) + { + ModelResourceLocation memory = new ModelResourceLocation(s, "inventory"); + try + { + IModel model = getModel(new ResourceLocation("forge", "item/bucket")); + // only on successful load, otherwise continue using the old model + stateModels.put(memory, model); + } + catch(IOException e) + { + // use the original vanilla model + } + } + + setBucketModel(Items.water_bucket); + setBucketModel(Items.lava_bucket); + // milk bucket only replaced if some mod adds milk + if(FluidRegistry.isFluidRegistered("milk")) + { + // can the milk be put into a bucket? + Fluid milk = FluidRegistry.getFluid("milk"); + FluidStack milkStack = new FluidStack(milk, FluidContainerRegistry.BUCKET_VOLUME); + if(FluidContainerRegistry.getContainerCapacity(milkStack, new ItemStack(Items.bucket)) == FluidContainerRegistry.BUCKET_VOLUME) + { + setBucketModel(Items.milk_bucket); + } + } + else + { + // milk bucket if no milk fluid is present + for(String s : getVariantNames(Items.milk_bucket)) + { + ModelResourceLocation memory = new ModelResourceLocation(s, "inventory"); + try + { + IModel model = getModel(new ResourceLocation("forge", "item/bucket_milk")); + // only on successful load, otherwise continue using the old model + stateModels.put(memory, model); + } + catch(IOException e) + { + // use the original vanilla model + } + } + } + } + } + + private void setBucketModel(Item item) + { + for(String s : getVariantNames(item)) + { + ModelResourceLocation memory = new ModelResourceLocation(s, "inventory"); + IModel model = stateModels.get(ModelDynBucket.LOCATION); + stateModels.put(memory, model); + } } public IModel getModel(ResourceLocation location) throws IOException diff --git a/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java b/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java index ed36a77b4..c50082751 100644 --- a/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java +++ b/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java @@ -34,6 +34,7 @@ public class ModelLoaderRegistry registerLoader(ModelFluid.FluidLoader.instance); registerLoader(ItemLayerModel.Loader.instance); registerLoader(MultiLayerModel.Loader.instance); + registerLoader(ModelDynBucket.LoaderDynBucket.instance); } /* diff --git a/src/main/java/net/minecraftforge/common/ForgeModContainer.java b/src/main/java/net/minecraftforge/common/ForgeModContainer.java index 48d4ab36b..f5ab3bfaa 100644 --- a/src/main/java/net/minecraftforge/common/ForgeModContainer.java +++ b/src/main/java/net/minecraftforge/common/ForgeModContainer.java @@ -5,6 +5,7 @@ package net.minecraftforge.common; +import static net.minecraftforge.common.config.Configuration.CATEGORY_CLIENT; import static net.minecraftforge.common.config.Configuration.CATEGORY_GENERAL; import java.io.File; @@ -68,6 +69,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC public static int defaultSpawnFuzz = 20; public static boolean defaultHasSpawnFuzz = true; public static boolean forgeLightPipelineEnabled = true; + public static boolean replaceVanillaBucketModel = true; private static Configuration config; private static ForgeModContainer INSTANCE; @@ -244,6 +246,16 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC config.setCategoryPropertyOrder(VERSION_CHECK_CAT, propOrder); + // Client-Side only properties + propOrder = new ArrayList(); + prop = config.get(Configuration.CATEGORY_CLIENT, "replaceVanillaBucketModel", Boolean.FALSE, + "Replace the vanilla bucket models with Forges own dynamic bucket model. Unifies bucket visuals if a mod uses the Forge bucket model."); + prop.setLanguageKey("forge.configgui.replaceBuckets").setRequiresMcRestart(true); + replaceVanillaBucketModel = prop.getBoolean(Boolean.FALSE); + propOrder.add(prop.getName()); + + config.setCategoryPropertyOrder(CATEGORY_CLIENT, propOrder); + if (config.hasChanged()) { config.save(); diff --git a/src/main/java/net/minecraftforge/common/config/Configuration.java b/src/main/java/net/minecraftforge/common/config/Configuration.java index 09733b49b..16aac45b5 100644 --- a/src/main/java/net/minecraftforge/common/config/Configuration.java +++ b/src/main/java/net/minecraftforge/common/config/Configuration.java @@ -48,6 +48,7 @@ import net.minecraftforge.fml.relauncher.FMLInjectionData; public class Configuration { public static final String CATEGORY_GENERAL = "general"; + public static final String CATEGORY_CLIENT = "client"; public static final String ALLOWED_CHARS = "._-"; public static final String DEFAULT_ENCODING = "UTF-8"; public static final String CATEGORY_SPLITTER = "."; diff --git a/src/main/resources/assets/forge/blockstates/dynbucket.json b/src/main/resources/assets/forge/blockstates/dynbucket.json new file mode 100644 index 000000000..9f240d8e9 --- /dev/null +++ b/src/main/resources/assets/forge/blockstates/dynbucket.json @@ -0,0 +1,18 @@ +{ + "forge_marker": 1, + "variants": { + "inventory": { + "model": "forge:forgebucket", + "textures": { + "base": "forge:items/bucket_base", + "fluid": "forge:items/bucket_fluid", + "cover": "forge:items/bucket_cover" + }, + "transform": "forge:default-item", + "custom": { + "fluid": "water", + "flipGas": true + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/forge/lang/en_US.lang b/src/main/resources/assets/forge/lang/en_US.lang index 055f15463..fed5d696b 100644 --- a/src/main/resources/assets/forge/lang/en_US.lang +++ b/src/main/resources/assets/forge/lang/en_US.lang @@ -12,6 +12,8 @@ forge.update.beta.2=Major issues may arise, verify before reporting. forge.configgui.forgeConfigTitle=Minecraft Forge Configuration forge.configgui.ctgy.forgeGeneralConfig.tooltip=This is where you can edit the settings contained in forge.cfg. forge.configgui.ctgy.forgeGeneralConfig=General Settings +forge.configgui.ctgy.forgeClientConfig.tooltip=Settings in the forge.cfg that only concern the local client. Usually graphical settings. +forge.configgui.ctgy.forgeClientConfig=Client Settings forge.configgui.ctgy.forgeChunkLoadingConfig.tooltip=This is where you can edit the settings contained in forgeChunkLoading.cfg. forge.configgui.ctgy.forgeChunkLoadingConfig=Forge Chunk Loader Default Settings forge.configgui.ctgy.forgeChunkLoadingModConfig.tooltip=This is where you can define mod-specific override settings that will be used instead of the defaults. @@ -43,6 +45,7 @@ forge.configgui.zombieBaseSummonChance.tooltip=Base zombie summoning spawn chanc forge.configgui.zombieBaseSummonChance=Zombie Summon Chance forge.configgui.stencilbits=Enable GL Stencil Bits forge.configgui.spawnfuzz=Respawn Fuzz Diameter +forge.configgui.replaceBuckets=Use Forges' bucket model forge.configgui.modID.tooltip=The mod ID that you want to define override settings for. forge.configgui.modID=Mod ID diff --git a/src/main/resources/assets/forge/models/item/bucket.json b/src/main/resources/assets/forge/models/item/bucket.json new file mode 100644 index 000000000..7a32f750a --- /dev/null +++ b/src/main/resources/assets/forge/models/item/bucket.json @@ -0,0 +1,18 @@ +{ + "parent": "builtin/generated", + "textures": { + "layer0": "forge:items/bucket_base" + }, + "display": { + "thirdperson": { + "rotation": [ -90, 0, 0 ], + "translation": [ 0, 1, -3 ], + "scale": [ 0.55, 0.55, 0.55 ] + }, + "firstperson": { + "rotation": [ 0, -135, 25 ], + "translation": [ 0, 4, 2 ], + "scale": [ 1.7, 1.7, 1.7 ] + } + } +} diff --git a/src/main/resources/assets/forge/models/item/bucket_milk.json b/src/main/resources/assets/forge/models/item/bucket_milk.json new file mode 100644 index 000000000..2d1fe26eb --- /dev/null +++ b/src/main/resources/assets/forge/models/item/bucket_milk.json @@ -0,0 +1,20 @@ +{ + "parent": "builtin/generated", + "textures": { + "layer0": "forge:items/bucket_base", + "layer1": "forge:items/bucket_fluid", # doubles as milk + "layer2": "forge:items/bucket_cover" + }, + "display": { + "thirdperson": { + "rotation": [ -90, 0, 0 ], + "translation": [ 0, 1, -3 ], + "scale": [ 0.55, 0.55, 0.55 ] + }, + "firstperson": { + "rotation": [ 0, -135, 25 ], + "translation": [ 0, 4, 2 ], + "scale": [ 1.7, 1.7, 1.7 ] + } + } +} diff --git a/src/main/resources/assets/forge/textures/items/bucket_base.png b/src/main/resources/assets/forge/textures/items/bucket_base.png new file mode 100644 index 0000000000000000000000000000000000000000..04faf510288e9496b6b121f5c6b85255baefc203 GIT binary patch literal 597 zcmV-b0;>IqP)VGd000McNliru-wY2FHX`+WA}{~|0pdwS zK~y-)rIWEsBS93#e{XiOnZ!NpgtQ@z#VPh82o|SZiu^U_zhkaTW#QR*3X)ie*NLEA z3}PS&tGn*(&RpT$szl+yDIYVx_r33Z1D`zNz5f(IzgOJY*eI#axw2jqg`A$Aeym`9 zecb~rFE3+^VIhhTpc00JVaU&e0|1VXk7W+9xw+|=mXA#dyn^?Z`<44 z4u^Q}KUR=s8I4ARuPZBT@9Z!hj~Nb!WKU1rjz(y$vDQ+pR!e|-wIZ&suZg0FX0u5g z#{@wDK%VDlttpBE=Nzv$S1Rz{Ga8Kuf`El6!WhGJI(@$M_p#QRhldBOwJ!=p1QDUv z>&gE9K53d#6a`t9p|z$`sgw->uCA^)KR*`$5qXPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;VI^GGzb&0n|xEK~y+TrINdD8bK6>{RsO8`vlzY0<;tq zT!gq6fCPv;#bU*xqd=mdii^02p^3XJu*5MEu@oy-+{L2B4Nt)%{FyV+uDpvIE5#?x zoSFYWmzgsF|HYfZ_wZ&g91cSx7!2x|?-7QLcG2haIUNp%)8p|tTdkJV@Asunrz3?z zArP-M`XUmEz-qN}p{lB)qA22WxfH9_s?caOgi56%wA*cottDp6BqoD&It@BlEEb{` zGsCJ8yk0L@61M#p(+{)RERM$`;_>({kx1;cxJELW-1+@}l*?tfTrL(e8RYYM91aHr z0s)~`tI2M+8G7wZg~COH3vce7Lv(n*r+eI-ixM(+y|_^Z6Xx z?H0eEp77)To>xEyRKei<9KBu-rBVrYyZzLFNTpI(uh;nX_=xX!cUY+^K3!dL18RW` zXdbUM1_OG^*=)vzUv6&j`TCldKO7Eu3+zuA`X>wqL?)BLcs#~vG~&pz4EBN0U@u_K xP8c>Cg+c+bSPao$)RUX toangD5F_ subItems) + { + for (int i = 0; i < 4; i++) + { + ItemStack bucket = new ItemStack(this, 1, i); + if (FluidContainerRegistry.isFilledContainer(bucket)) + subItems.add(bucket); + } + } + } + + public static class DynBottle extends Item + { + public DynBottle() + { + setUnlocalizedName("dynbottle"); + setMaxStackSize(1); + setHasSubtypes(true); + setCreativeTab(CreativeTabs.tabMisc); + } + + @Override + public void getSubItems(Item itemIn, CreativeTabs tab, List subItems) + { + for (int i = 0; i < 4; i++) + { + ItemStack bucket = new ItemStack(this, 1, i); + if (FluidContainerRegistry.isFilledContainer(bucket)) + subItems.add(bucket); + } + } + } +} diff --git a/src/test/java/net/minecraftforge/debug/ModelFluidDebug.java b/src/test/java/net/minecraftforge/debug/ModelFluidDebug.java index 537eb3817..808da524d 100644 --- a/src/test/java/net/minecraftforge/debug/ModelFluidDebug.java +++ b/src/test/java/net/minecraftforge/debug/ModelFluidDebug.java @@ -29,6 +29,8 @@ public class ModelFluidDebug @SidedProxy(serverSide = "net.minecraftforge.debug.ModelFluidDebug$CommonProxy", clientSide = "net.minecraftforge.debug.ModelFluidDebug$ClientProxy") public static CommonProxy proxy; + public static final Fluid milkFluid = new Fluid("milk", new ResourceLocation("forge", "blocks/milk_still"), new ResourceLocation("forge", "blocks/milk_flow")); + @EventHandler public void preInit(FMLPreInitializationEvent event) { proxy.preInit(event); } @@ -38,8 +40,10 @@ public class ModelFluidDebug { FluidRegistry.registerFluid(TestFluid.instance); FluidRegistry.registerFluid(TestGas.instance); + FluidRegistry.registerFluid(milkFluid); GameRegistry.registerBlock(TestFluidBlock.instance, TestFluidBlock.name); GameRegistry.registerBlock(TestGasBlock.instance, TestGasBlock.name); + GameRegistry.registerBlock(MilkFluidBlock.instance, MilkFluidBlock.name); } } @@ -47,6 +51,7 @@ public class ModelFluidDebug { private static ModelResourceLocation fluidLocation = new ModelResourceLocation(MODID.toLowerCase() + ":" + TestFluidBlock.name, "fluid"); private static ModelResourceLocation gasLocation = new ModelResourceLocation(MODID.toLowerCase() + ":" + TestFluidBlock.name, "gas"); + private static ModelResourceLocation milkLocation = new ModelResourceLocation(MODID.toLowerCase() + ":" + TestFluidBlock.name, "milk"); @Override public void preInit(FMLPreInitializationEvent event) @@ -54,8 +59,10 @@ public class ModelFluidDebug super.preInit(event); Item fluid = Item.getItemFromBlock(TestFluidBlock.instance); Item gas = Item.getItemFromBlock(TestGasBlock.instance); + Item milk = Item.getItemFromBlock(MilkFluidBlock.instance); ModelBakery.addVariantName(fluid); ModelBakery.addVariantName(gas); + ModelBakery.addVariantName(milk); ModelLoader.setCustomMeshDefinition(fluid, new ItemMeshDefinition() { public ModelResourceLocation getModelLocation(ItemStack stack) @@ -70,6 +77,13 @@ public class ModelFluidDebug return gasLocation; } }); + ModelLoader.setCustomMeshDefinition(milk, new ItemMeshDefinition() + { + public ModelResourceLocation getModelLocation(ItemStack stack) + { + return milkLocation; + } + }); ModelLoader.setCustomStateMapper(TestFluidBlock.instance, new StateMapperBase() { protected ModelResourceLocation getModelResourceLocation(IBlockState state) @@ -84,6 +98,13 @@ public class ModelFluidDebug return gasLocation; } }); + ModelLoader.setCustomStateMapper(MilkFluidBlock.instance, new StateMapperBase() + { + protected ModelResourceLocation getModelResourceLocation(IBlockState state) + { + return milkLocation; + } + }); } } @@ -136,6 +157,19 @@ public class ModelFluidDebug } } + public static final class MilkFluidBlock extends BlockFluidClassic + { + public static final MilkFluidBlock instance = new MilkFluidBlock(); + public static final String name = "MilkFluidBlock"; + + private MilkFluidBlock() + { + super(milkFluid, Material.water); + setCreativeTab(CreativeTabs.tabBlock); + setUnlocalizedName(MODID + ":" + name); + } + } + public static final class TestGasBlock extends BlockFluidClassic { public static final TestGasBlock instance = new TestGasBlock(); diff --git a/src/test/resources/assets/forge/blockstates/dynbottle.json b/src/test/resources/assets/forge/blockstates/dynbottle.json new file mode 100644 index 000000000..98fa082fe --- /dev/null +++ b/src/test/resources/assets/forge/blockstates/dynbottle.json @@ -0,0 +1,18 @@ +{ + "forge_marker": 1, + "variants": { + "inventory": { + "model": "forge:forgebucket", + "textures": { + "base": "minecraft:items/potion_bottle_empty", + "fluid": "minecraft:items/potion_overlay", + "cover": "minecraft:items/potion_bottle_empty" + }, + "transform": "forge:default-item", + "custom": { + "fluid": "water", + "flipGas": false + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/assets/forge/textures/blocks/milk_flow.png b/src/test/resources/assets/forge/textures/blocks/milk_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..e28842b6bd8c45719a2e6630229a8ffd5cacdd58 GIT binary patch literal 5163 zcmYjV2|UyP|EHxnk{pp7Blo^ixn;Jssa7?8mvn+05Z5l2-YSA$rV<9?YNO+-5xd#s$oS#@`0;n_KkD05))u6<>$R=K?{-xy*%<$6Y02kIAqYWgCGpbz2t-^Q zY@g&t;msc&V~m95(s+KL#mTHdj%EM&7$04;Setho@#jy|=2J#HZ6ETJwY2 z$CJ_2QHTe+J!anKpC6|@D7Q@;gNfG#_phfF#$ERKfdyS$D&liXkg`7ya=IaxU$mT? z5~)+WJDFC2lZ#a!8!?+XZt=yCem4Cj#_ugpLCJ@Ja4I+2ODnvIE)Z<;#Dy+QYk7m$ z;CbSwh}6_+ISb{3oziW)7RWcJ0wO{~PCovpwhQ7`ZgOv06El9*9v06Z7%{u%WEna$ z+8jL~MG>NzoS?^Kku`UU_SVFG^?)xXYR>SU;~HbK((nnE7W2~ZLtW3oakh|Yul0^? zkkuhL64=a<7ZR;c(DSROu4MngagallrMsxec*Y_yolgdZ&z~U=pRuw&xV0A4l&al9HB|PHx*&)r;Q3V}$xJDFC$GA%`YP`f%Q~qH zeDHx6E=rT2X%}==^p&c*1`)BN#H87zR3y~S#tvpqMTwnvy9?2vVsfW?xWp9h3$OWR z^CRW!1JSx3#9JcAC}y)krnzBG9No2U=@i*lVQsw+U#{uidtz=~LBr++8ll=SpA>6+ zs#{x6-l`s+S=#VqTQ{=ubNe5;w0hw@ulLN>UoIGQG@LV}Xym+UCdMq9(ZUa%tc$+S zF7@D5OFV)iWf!#->1;q>Z>%a?ct4PX6Fgh+t!tXm)RP`t_ zaSI;Qw%_}0qW{A4IcG8P{0p!tcBLlTO)R#}zg&K5Lx%`{{VqQUWih+q%3c-~y&&|r z;@nBSCg}Vv!4qn2H8AAW=Y|wnQU29cF^8XvwE=9qL%lSggFU+zq;eeIvePBZCP zNX$40i$3qȲ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 Y>0Y`}VA*cqT2H|CxRd35l>fE=12(~?d;kCd literal 0 HcmV?d00001 diff --git a/src/test/resources/assets/forge/textures/blocks/milk_flow.png.mcmeta b/src/test/resources/assets/forge/textures/blocks/milk_flow.png.mcmeta new file mode 100644 index 000000000..4f0718ac9 --- /dev/null +++ b/src/test/resources/assets/forge/textures/blocks/milk_flow.png.mcmeta @@ -0,0 +1,3 @@ +{ + "animation": {} +} \ No newline at end of file diff --git a/src/test/resources/assets/forge/textures/blocks/milk_still.png b/src/test/resources/assets/forge/textures/blocks/milk_still.png new file mode 100644 index 0000000000000000000000000000000000000000..d83dba2a83d37348c5a29c36c0c29c0589b4ba9a GIT binary patch literal 6438 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 AcK`qY literal 0 HcmV?d00001 diff --git a/src/test/resources/assets/forge/textures/blocks/milk_still.png.mcmeta b/src/test/resources/assets/forge/textures/blocks/milk_still.png.mcmeta new file mode 100644 index 000000000..0645f48c6 --- /dev/null +++ b/src/test/resources/assets/forge/textures/blocks/milk_still.png.mcmeta @@ -0,0 +1,5 @@ +{ + "animation": { + "frametime": 2 + } +} diff --git a/src/test/resources/assets/forgedebugmodelfluid/blockstates/TestFluidBlock.json b/src/test/resources/assets/forgedebugmodelfluid/blockstates/TestFluidBlock.json index 7be998c4b..bce5d7ca0 100644 --- a/src/test/resources/assets/forgedebugmodelfluid/blockstates/TestFluidBlock.json +++ b/src/test/resources/assets/forgedebugmodelfluid/blockstates/TestFluidBlock.json @@ -8,6 +8,10 @@ "gas": { "model": "forge:fluid", "custom": { "fluid": "testgas" } + }, + "milk": { + "model": "forge:fluid", + "custom": { "fluid": "milk" } } } }