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 b4c8dc5f2..7aa3d5f77 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 000000000..04faf5102 Binary files /dev/null and b/src/main/resources/assets/forge/textures/items/bucket_base.png differ diff --git a/src/main/resources/assets/forge/textures/items/bucket_cover.png b/src/main/resources/assets/forge/textures/items/bucket_cover.png new file mode 100644 index 000000000..863787ae1 Binary files /dev/null and b/src/main/resources/assets/forge/textures/items/bucket_cover.png differ diff --git a/src/main/resources/assets/forge/textures/items/bucket_fluid.png b/src/main/resources/assets/forge/textures/items/bucket_fluid.png new file mode 100644 index 000000000..d03389aab Binary files /dev/null and b/src/main/resources/assets/forge/textures/items/bucket_fluid.png differ diff --git a/src/test/java/net/minecraftforge/debug/DynBucketTest.java b/src/test/java/net/minecraftforge/debug/DynBucketTest.java new file mode 100644 index 000000000..defc191a3 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/DynBucketTest.java @@ -0,0 +1,179 @@ +package net.minecraftforge.debug; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.renderer.ItemMeshDefinition; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.ModelDynBucket; +import net.minecraftforge.client.model.ModelLoader; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.debug.ModelFluidDebug.TestFluid; +import net.minecraftforge.debug.ModelFluidDebug.TestGas; +import net.minecraftforge.event.entity.player.FillBucketEvent; +import net.minecraftforge.fluids.*; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.SidedProxy; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.eventhandler.Event.Result; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.registry.GameRegistry; + +import java.util.List; + +@Mod(modid = "DynBucketTest", version = "0.1", dependencies = "after:" + ModelFluidDebug.MODID) +public class DynBucketTest +{ + public static final Item dynBucket = new DynBucket(); + public static final Item dynBottle = new DynBottle(); + + @SidedProxy(clientSide = "net.minecraftforge.debug.DynBucketTest$ClientProxy", serverSide = "net.minecraftforge.debug.DynBucketTest$CommonProxy") + public static CommonProxy proxy; + + public static class CommonProxy + { + void setupModels() + { + } + } + + public static class ClientProxy extends CommonProxy + { + @Override + void setupModels() + { + ModelLoader.setCustomMeshDefinition(dynBucket, new ItemMeshDefinition() + { + @Override + public ModelResourceLocation getModelLocation(ItemStack stack) + { + return ModelDynBucket.LOCATION; + } + }); + ModelBakery.addVariantName(dynBucket, "forge:dynbucket"); + + ModelLoader.setCustomMeshDefinition(dynBottle, new ItemMeshDefinition() + { + @Override + public ModelResourceLocation getModelLocation(ItemStack stack) + { + return new ModelResourceLocation(new ResourceLocation("forge", "dynbottle"), "inventory"); + } + }); + ModelBakery.addVariantName(dynBottle, "forge:dynbottle"); + } + } + + @EventHandler + public void preInit(FMLPreInitializationEvent event) + { + GameRegistry.registerItem(dynBucket, "dynbucket"); + GameRegistry.registerItem(dynBottle, "dynbottle"); + + // register fluid containers + int i = 0; + //registerFluidContainer(FluidRegistry.WATER, i++); + //registerFluidContainer(FluidRegistry.LAVA, i++); + registerFluidContainer(FluidRegistry.getFluid(TestFluid.name), i++); + registerFluidContainer(FluidRegistry.getFluid(TestGas.name), i++); + + i = 0; + registerFluidContainer2(FluidRegistry.WATER, i++); + registerFluidContainer2(FluidRegistry.LAVA, i++); + registerFluidContainer2(FluidRegistry.getFluid(TestFluid.name), i++); + registerFluidContainer2(FluidRegistry.getFluid(TestGas.name), i++); + + // Set TestFluidBlocks blockstate to use milk instead of testfluid for the texture to be loaded + FluidContainerRegistry.registerFluidContainer(FluidRegistry.getFluid("milk"), new ItemStack(Items.milk_bucket), FluidContainerRegistry.EMPTY_BUCKET); + + proxy.setupModels(); + MinecraftForge.EVENT_BUS.register(this); + } + + private void registerFluidContainer(Fluid fluid, int meta) + { + if (fluid == null) + return; + + FluidStack fs = new FluidStack(fluid, FluidContainerRegistry.BUCKET_VOLUME); + ItemStack stack = new ItemStack(dynBucket, 1, meta); + FluidContainerRegistry.registerFluidContainer(fs, stack, new ItemStack(Items.bucket)); + } + + private void registerFluidContainer2(Fluid fluid, int meta) + { + if (fluid == null) + return; + + FluidStack fs = new FluidStack(fluid, 250); + ItemStack stack = new ItemStack(dynBottle, 1, meta); + FluidContainerRegistry.registerFluidContainer(fs, stack, new ItemStack(Items.glass_bottle)); + } + + @SubscribeEvent + public void onBucketFill(FillBucketEvent event) + { + IBlockState state = event.world.getBlockState(event.target.getBlockPos()); + if (state.getBlock() instanceof IFluidBlock) + { + Fluid fluid = ((IFluidBlock) state.getBlock()).getFluid(); + FluidStack fs = new FluidStack(fluid, FluidContainerRegistry.BUCKET_VOLUME); + + ItemStack filled = FluidContainerRegistry.fillFluidContainer(fs, event.current); + if (filled != null) + { + event.result = filled; + event.setResult(Result.ALLOW); + } + } + } + + public static class DynBucket extends Item + { + public DynBucket() + { + setUnlocalizedName("dynbucket"); + 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); + } + } + } + + 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 000000000..e28842b6b Binary files /dev/null and b/src/test/resources/assets/forge/textures/blocks/milk_flow.png differ 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 000000000..d83dba2a8 Binary files /dev/null and b/src/test/resources/assets/forge/textures/blocks/milk_still.png differ 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" } } } }