Merge pull request #2282 from bonii-xx/dynbucket

Add a dynamic bucket model that displays the animated liquid contained
This commit is contained in:
Fry committed 2015-12-22 00:26:25 +03:00
commit c7790f7b35
22 files changed
+958

No files matched your search

@@ -104,6 +104,7 @@ public class ForgeGuiFactory implements IModGuiFactory
{
List<IConfigElement> list = new ArrayList<IConfigElement>();
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.
@@ -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.
* </br>
* 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<UnpackedBakedQuad> convertTexture(VertexFormat format, TRSRTransformation transform, TextureAtlasSprite template, TextureAtlasSprite sprite, float z, EnumFacing facing, int color)
{
List<UnpackedBakedQuad> horizontal = convertTextureHorizontal(format, transform, template, sprite, z, facing, color);
List<UnpackedBakedQuad> 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<UnpackedBakedQuad> 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<UnpackedBakedQuad> 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<UnpackedBakedQuad> 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<UnpackedBakedQuad> 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;
}
}
}
}
@@ -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<ResourceLocation> getDependencies()
{
return ImmutableList.of();
}
@Override
public Collection<ResourceLocation> getTextures()
{
ImmutableSet.Builder<ResourceLocation> 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<ResourceLocation, TextureAtlasSprite> bakedTextureGetter)
{
ImmutableMap<TransformType, TRSRTransformation> 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.<IModelPart>absent()).or(TRSRTransformation.identity());
TextureAtlasSprite fluidSprite = bakedTextureGetter.apply(fluid.getStill());
ImmutableList.Builder<BakedQuad> 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.<String, IFlexibleBakedModel>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
* <p/>
* If the fluid can't be found, water is used
*/
@Override
public IModel process(ImmutableMap<String, String> 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)
* <p/>
* If no liquid is given a hardcoded variant for the bucket is used.
*/
@Override
public IModel retexture(ImmutableMap<String, String> 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<String, IFlexibleBakedModel> cache; // contains all the baked models since they'll never change
private final ImmutableMap<TransformType, TRSRTransformation> transforms;
public BakedDynBucket(ModelDynBucket parent,
ImmutableList<BakedQuad> quads, TextureAtlasSprite particle, VertexFormat format, ImmutableMap<ItemCameraTransforms.TransformType, TRSRTransformation> transforms,
Map<String, IFlexibleBakedModel> 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<ResourceLocation, TextureAtlasSprite> textureGetter;
textureGetter = new Function<ResourceLocation, TextureAtlasSprite>()
{
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<? extends IFlexibleBakedModel, Matrix4f> handlePerspective(TransformType cameraTransformType)
{
return IPerspectiveAwareModel.MapWrapper.handlePerspective(this, transforms, cameraTransformType);
}
}
}
@@ -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<String>)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
@@ -34,6 +34,7 @@ public class ModelLoaderRegistry
registerLoader(ModelFluid.FluidLoader.instance);
registerLoader(ItemLayerModel.Loader.instance);
registerLoader(MultiLayerModel.Loader.instance);
registerLoader(ModelDynBucket.LoaderDynBucket.instance);
}
/*
@@ -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<String>();
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();
@@ -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 = ".";